HarmonyOS开发:设备分享——设备权限管理

举报
Jack20 发表于 2026/06/26 16:00:13 2026/06/26
【摘要】 HarmonyOS开发:设备分享——设备权限管理📌 核心要点:设备分享是临时授权的利器,分享链接和二维码让朋友也能控制你的设备,权限粒度从查看到管理四级控制,分享有效期和撤销机制保安全。 背景与动机朋友来你家做客,想开空调。你不想把家庭管理员权限给他——那等于把你家的钥匙给了别人。但你也不想每次都帮他操作——你在厨房忙着做饭,没空跑过去帮他开空调。设备分享就是解决这个问题的。 临时把某个...

HarmonyOS开发:设备分享——设备权限管理

📌 核心要点:设备分享是临时授权的利器,分享链接和二维码让朋友也能控制你的设备,权限粒度从查看到管理四级控制,分享有效期和撤销机制保安全。

背景与动机

朋友来你家做客,想开空调。你不想把家庭管理员权限给他——那等于把你家的钥匙给了别人。但你也不想每次都帮他操作——你在厨房忙着做饭,没空跑过去帮他开空调。

设备分享就是解决这个问题的。 临时把某个设备的控制权分享给朋友,他可以自己开空调,但不能控制其他设备,也不能修改你的设置。朋友走了,分享自动过期,权限收回。

设备分享和家庭共享不一样。家庭共享是长期关系——家人共享所有设备。设备分享是临时授权——朋友临时控制某个设备。场景不同,权限模型也不同。

但设备分享做起来有几个关键问题:分享的权限粒度怎么控制?分享链接被转发给别人怎么办?分享过期后对方还在控制怎么办?设备主人撤销分享后对方知不知道?

核心原理

设备分享流程

flowchart TD
    A[设备主人选择分享] --> B[配置分享参数]
    
    B --> C[选择设备]
    B --> D[设置权限级别]
    B --> E[设置有效期]
    
    C --> F[生成分享凭证]
    D --> F
    E --> F
    
    F --> G{分享方式}
    G -->|分享链接| H[生成短链接]
    G -->|二维码| I[生成二维码]
    G -->|账号分享| J[指定华为账号]
    
    H --> K[对方点击链接]
    I --> L[对方扫码]
    J --> M[对方收到通知]
    
    K --> N[对方确认接受]
    L --> N
    M --> N
    
    N --> O[创建分享记录]
    O --> P[对方获得设备控制权限]
    
    P --> Q{权限使用}
    Q -->|查看| R[查看设备状态]
    Q -->|控制| S[控制设备属性]
    Q -->|管理| T[修改设备设置]
    
    P --> U{分享终止}
    U -->|到期自动失效| V[删除分享记录]
    U -->|主人主动撤销| W[通知对方,删除记录]
    U -->|对方主动退出| X[删除记录]
    
    classDef owner fill:#1565C0,color:#fff,stroke:#0D47A1
    classDef config fill:#2E7D32,color:#fff,stroke:#1B5E20
    classDef share fill:#E65100,color:#fff,stroke:#BF360C
    classDef use fill:#6A1B9A,color:#fff,stroke:#4A148C
    classDef end_ fill:#C62828,color:#fff,stroke:#B71C1C

    class A,B,C,D,E,owner
    class F,G,H,I,J,config
    class K,L,M,N,O,P,share
    class Q,R,S,T,use
    class U,V,W,X,end_

权限粒度

设备分享的权限分四级:

权限级别 说明 可执行操作
查看(view) 只能看不能动 查看设备状态、查看设备信息
控制(control) 可以控制但不能配置 查看 + 控制设备属性(开/关/调温等)
配置(config) 可以修改设备设置 控制 + 修改设备名称、修改设备参数
管理(admin) 完全控制权 配置 + 分享设备给其他人、删除设备

实际项目中,"查看"和"控制"用得最多。 朋友来家里,给他"控制"权限就行——他能开空调,但不能改空调的名字。保姆来打扫,给她"查看"权限——她能看到灯是不是开着,但不能控制。

分享有效期

有效期 适用场景
1小时 临时访客
24小时 朋友来家里做客
7天 亲戚来住几天
30天 长期租客
永久 长期信任的人(不推荐)

分享到期后,权限自动收回,对方无法继续控制设备。

分享安全

分享链接和二维码可能被转发。你把空调控制权分享给朋友A,A把链接转发给B,B也能控制你的空调——这显然不行。

解法:

  1. 绑定账号:分享时指定对方账号,只有该账号可以使用
  2. 使用次数限制:分享链接只能使用一次,使用后失效
  3. IP绑定:分享链接只能在特定IP段使用(如你家WiFi下)
  4. 二次确认:对方使用分享链接时,需要主人再次确认

代码实战

基础用法:设备分享服务

设备分享的创建、验证、权限检查、撤销。

// services/DeviceShareService.ets
// 设备分享服务 - 分享创建、权限验证、分享管理

// 分享权限级别
type SharePermission = 'view' | 'control' | 'config' | 'admin'

// 分享记录
interface DeviceShare {
  shareId: string
  deviceId: string           // 被分享的设备ID
  deviceName: string         // 设备名称
  ownerId: string            // 设备主人ID
  ownerName: string          // 主人名称
  targetUserId?: string      // 目标用户ID(账号分享时)
  targetName?: string        // 目标用户名称
  permission: SharePermission // 权限级别
  shareCode: string          // 分享码(6位)
  shareLink?: string         // 分享链接
  expiresAt: number          // 过期时间(0=永久)
  createdAt: number
  usedAt?: number            // 使用时间
  status: 'pending' | 'active' | 'expired' | 'revoked'
  maxUseCount: number        // 最大使用次数
  usedCount: number          // 已使用次数
}

// 权限检查结果
interface SharePermissionCheck {
  hasPermission: boolean
  permission?: SharePermission
  reason?: string
}

class DeviceShareService {
  // 分享记录
  private shares: Map<string, DeviceShare> = new Map()
  // 我收到的分享
  private receivedShares: Map<string, DeviceShare> = new Map()

  // 创建设备分享
  async createShare(params: {
    deviceId: string
    deviceName: string
    targetUserId?: string
    targetName?: string
    permission: SharePermission
    expiresIn?: number       // 有效期(小时),0=永久
    maxUseCount?: number     // 最大使用次数
  }): Promise<DeviceShare> {
    const shareCode = this.generateShareCode()
    const expiresAt = params.expiresIn
      ? Date.now() + params.expiresIn * 60 * 60 * 1000
      : 0

    const share: DeviceShare = {
      shareId: `share_${Date.now()}`,
      deviceId: params.deviceId,
      deviceName: params.deviceName,
      ownerId: 'user_001',
      ownerName: '我',
      targetUserId: params.targetUserId,
      targetName: params.targetName,
      permission: params.permission,
      shareCode: shareCode,
      expiresAt: expiresAt,
      createdAt: Date.now(),
      status: 'pending',
      maxUseCount: params.maxUseCount || 1,
      usedCount: 0
    }

    // 生成分享链接
    share.shareLink = `smarthome://share?code=${shareCode}`

    this.shares.set(share.shareId, share)
    return share
  }

  // 使用分享码接受分享
  async acceptShare(shareCode: string): Promise<DeviceShare | null> {
    // 查找对应的分享记录
    let foundShare: DeviceShare | null = null
    this.shares.forEach(share => {
      if (share.shareCode === shareCode) {
        foundShare = share
      }
    })

    if (!foundShare) {
      return null
    }

    // 检查分享是否有效
    if (foundShare.status !== 'pending') {
      return null
    }

    // 检查是否过期
    if (foundShare.expiresAt > 0 && Date.now() > foundShare.expiresAt) {
      foundShare.status = 'expired'
      return null
    }

    // 检查使用次数
    if (foundShare.usedCount >= foundShare.maxUseCount) {
      return null
    }

    // 检查目标用户(如果指定了)
    if (foundShare.targetUserId && foundShare.targetUserId !== 'user_guest') {
      return null
    }

    // 接受分享
    foundShare.status = 'active'
    foundShare.usedAt = Date.now()
    foundShare.usedCount++

    // 添加到我收到的分享
    this.receivedShares.set(foundShare.shareId, { ...foundShare })

    return foundShare
  }

  // 检查分享权限
  checkPermission(userId: string, deviceId: string, action: string): SharePermissionCheck {
    // 先检查是否是设备主人
    // 实际项目中从设备管理服务获取
    if (userId === 'user_001') {
      return { hasPermission: true, permission: 'admin' }
    }

    // 检查分享权限
    let highestPermission: SharePermission | null = null

    this.receivedShares.forEach(share => {
      if (share.deviceId !== deviceId) return
      if (share.status !== 'active') return

      // 检查过期
      if (share.expiresAt > 0 && Date.now() > share.expiresAt) {
        share.status = 'expired'
        return
      }

      // 检查权限是否足够
      if (this.isPermissionSufficient(share.permission, action)) {
        if (!highestPermission || this.comparePermissions(share.permission, highestPermission) > 0) {
          highestPermission = share.permission
        }
      }
    })

    if (highestPermission) {
      return { hasPermission: true, permission: highestPermission }
    }

    return { hasPermission: false, reason: '您没有该设备的操作权限' }
  }

  // 判断权限是否足够
  private isPermissionSufficient(permission: SharePermission, action: string): boolean {
    const actionPermissionMap: Record<string, SharePermission> = {
      'view': 'view',
      'control': 'control',
      'config': 'config',
      'share': 'admin'
    }

    const requiredPermission = actionPermissionMap[action]
    if (!requiredPermission) return false

    return this.comparePermissions(permission, requiredPermission) >= 0
  }

  // 比较权限级别
  private comparePermissions(a: SharePermission, b: SharePermission): number {
    const levels: Record<SharePermission, number> = {
      'view': 1,
      'control': 2,
      'config': 3,
      'admin': 4
    }
    return levels[a] - levels[b]
  }

  // 撤销分享
  revokeShare(shareId: string): boolean {
    const share = this.shares.get(shareId)
    if (!share) return false

    share.status = 'revoked'

    // 同步更新对方收到的分享
    const received = this.receivedShares.get(shareId)
    if (received) {
      received.status = 'revoked'
    }

    return true
  }

  // 获取我分享出去的记录
  getMyShares(): DeviceShare[] {
    return Array.from(this.shares.values())
  }

  // 获取我收到的分享
  getReceivedShares(): DeviceShare[] {
    // 过滤掉过期和撤销的
    return Array.from(this.receivedShares.values()).filter(share => {
      if (share.status === 'revoked') return false
      if (share.expiresAt > 0 && Date.now() > share.expiresAt) {
        share.status = 'expired'
        return false
      }
      return true
    })
  }

  // 清理过期分享
  cleanExpiredShares(): number {
    let cleaned = 0
    this.shares.forEach((share, shareId) => {
      if (share.expiresAt > 0 && Date.now() > share.expiresAt && share.status === 'active') {
        share.status = 'expired'
        cleaned++
      }
    })
    return cleaned
  }

  // 生成6位分享码
  private generateShareCode(): string {
    const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'  // 去掉容易混淆的字符
    let code = ''
    for (let i = 0; i < 6; i++) {
      code += chars.charAt(Math.floor(Math.random() * chars.length))
    }
    return code
  }
}

export default new DeviceShareService()
export type { DeviceShare, SharePermission }

进阶用法:分享二维码生成

用鸿蒙的二维码组件生成分享二维码,方便线下扫码分享。

// components/ShareQrCode.ets
// 分享二维码组件

import { qrcode } from '@kit.ScanKit'
import DeviceShareService, { DeviceShare } from '../services/DeviceShareService'

@Component
export struct ShareQrCode {
  share: DeviceShare | null = null
  onDismiss?: () => void

  build() {
    Column() {
      // 标题
      Text('设备分享')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })

      if (this.share) {
        // 设备信息
        Row() {
          Text(this.share.deviceName)
            .fontSize(16)
            .fontColor('#333333')
          Text(this.getPermissionLabel(this.share.permission))
            .fontSize(12)
            .fontColor('#007DFF')
            .backgroundColor('#F0F7FF')
            .borderRadius(8)
            .padding({ left: 8, right: 8, top: 2, bottom: 2 })
            .margin({ left: 8 })
        }
        .margin({ bottom: 16 })

        // 二维码
        Column() {
          QRCode(this.share.shareLink || this.share.shareCode)
            .width(200)
            .height(200)
            .color('#333333')
            .backgroundColor('#FFFFFF')
        }
        .padding(16)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .shadow({ radius: 8, color: '#1A000000', offsetY: 2 })

        // 分享码
        Text('分享码')
          .fontSize(12)
          .fontColor('#999999')
          .margin({ top: 16 })
        Text(this.share.shareCode)
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#007DFF')
          .letterSpacing(6)
          .margin({ top: 4 })

        // 有效期
        Text(this.getExpiresText(this.share))
          .fontSize(12)
          .fontColor('#999999')
          .margin({ top: 8 })

        // 提示
        Text('请让对方扫描二维码或输入分享码')
          .fontSize(13)
          .fontColor('#666666')
          .margin({ top: 16 })
      }

      // 关闭按钮
      Button('关闭')
        .width('90%')
        .height(44)
        .backgroundColor('#F5F5F5')
        .fontColor('#333333')
        .borderRadius(22)
        .margin({ top: 24 })
        .onClick(() => {
          this.onDismiss?.()
        })
    }
    .padding(24)
    .backgroundColor('#FFFFFF')
    .borderRadius({ topLeft: 24, topRight: 24 })
    .alignItems(HorizontalAlign.Center)
  }

  private getPermissionLabel(permission: string): string {
    const labels: Record<string, string> = {
      'view': '仅查看',
      'control': '可控制',
      'config': '可配置',
      'admin': '完全管理'
    }
    return labels[permission] || permission
  }

  private getExpiresText(share: DeviceShare): string {
    if (share.expiresAt === 0) return '永久有效'
    const remaining = share.expiresAt - Date.now()
    if (remaining <= 0) return '已过期'
    const hours = Math.ceil(remaining / (60 * 60 * 1000))
    if (hours < 24) return `${hours}小时后过期`
    const days = Math.ceil(hours / 24)
    return `${days}天后过期`
  }
}

完整示例:设备分享管理页面

分享创建、分享列表、接受分享、权限管理的完整UI。

// pages/DeviceSharePage.ets
// 设备分享管理页面

import DeviceShareService, { DeviceShare, SharePermission } from '../services/DeviceShareService'
import { ShareQrCode } from '../components/ShareQrCode'
import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct DeviceSharePage {
  @State myShares: DeviceShare[] = []
  @State receivedShares: DeviceShare[] = []
  @State showCreateDialog: boolean = false
  @State showQrCode: boolean = false
  @State currentShare: DeviceShare | null = null
  @State showAcceptDialog: boolean = false
  @State acceptCode: string = ''

  // 创建分享表单
  @State selectedDevice: string = 'light-001'
  @State selectedPermission: SharePermission = 'control'
  @State selectedExpiry: number = 24

  aboutToAppear() {
    this.loadShares()
  }

  loadShares() {
    this.myShares = DeviceShareService.getMyShares()
    this.receivedShares = DeviceShareService.getReceivedShares()
  }

  // 创建分享
  async createShare() {
    const deviceNames: Record<string, string> = {
      'light-001': '客厅灯',
      'ac-001': '卧室空调',
      'curtain-001': '客厅窗帘'
    }

    const share = await DeviceShareService.createShare({
      deviceId: this.selectedDevice,
      deviceName: deviceNames[this.selectedDevice] || this.selectedDevice,
      permission: this.selectedPermission,
      expiresIn: this.selectedExpiry,
      maxUseCount: 1
    })

    this.currentShare = share
    this.showCreateDialog = false
    this.showQrCode = true
    this.loadShares()
  }

  // 接受分享
  async acceptShare() {
    if (!this.acceptCode.trim()) {
      promptAction.showToast({ message: '请输入分享码' })
      return
    }

    const share = await DeviceShareService.acceptShare(this.acceptCode.toUpperCase())
    if (share) {
      this.showAcceptDialog = false
      this.acceptCode = ''
      this.loadShares()
      promptAction.showToast({ message: `已获得${share.deviceName}的控制权限` })
    } else {
      promptAction.showToast({ message: '分享码无效或已过期' })
    }
  }

  // 撤销分享
  revokeShare(shareId: string) {
    const success = DeviceShareService.revokeShare(shareId)
    if (success) {
      this.loadShares()
      promptAction.showToast({ message: '已撤销分享' })
    }
  }

  // 获取权限标签
  getPermissionLabel(permission: string): string {
    const labels: Record<string, string> = {
      'view': '仅查看',
      'control': '可控制',
      'config': '可配置',
      'admin': '完全管理'
    }
    return labels[permission] || permission
  }

  // 获取权限颜色
  getPermissionColor(permission: string): string {
    const colors: Record<string, string> = {
      'view': '#999999',
      'control': '#007DFF',
      'config': '#FF9800',
      'admin': '#F44336'
    }
    return colors[permission] || '#999999'
  }

  // 获取状态标签
  getStatusLabel(status: string): string {
    const labels: Record<string, string> = {
      'pending': '待接受',
      'active': '使用中',
      'expired': '已过期',
      'revoked': '已撤销'
    }
    return labels[status] || status
  }

  build() {
    Navigation() {
      Tabs({ index: 0 }) {
        TabContent() {
          this.MySharesTab()
        }.tabBar('我的分享')

        TabContent() {
          this.ReceivedSharesTab()
        }.tabBar('收到的分享')
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#F8F8F8')
    }
    .title('设备分享')
    .titleMode(NavigationTitleMode.Mini)
    .bindContentCover(this.showCreateDialog, this.CreateShareDialog(), {
      modalTransition: ModalTransition.DEFAULT
    })
    .bindContentCover(this.showQrCode, this.QrCodePanel(), {
      modalTransition: ModalTransition.DEFAULT
    })
    .bindContentCover(this.showAcceptDialog, this.AcceptShareDialog(), {
      modalTransition: ModalTransition.DEFAULT
    })
  }

  // 我的分享
  @Builder MySharesTab() {
    Column() {
      List({ space: 8 }) {
        ForEach(this.myShares, (share: DeviceShare) => {
          ListItem() {
            Row() {
              Column() {
                Text(share.deviceName)
                  .fontSize(15)
                  .fontColor('#333333')
                  .fontWeight(FontWeight.Medium)

                Row() {
                  Text(this.getPermissionLabel(share.permission))
                    .fontSize(12)
                    .fontColor(this.getPermissionColor(share.permission))
                    .backgroundColor(`${this.getPermissionColor(share.permission)}1A`)
                    .borderRadius(8)
                    .padding({ left: 6, right: 6, top: 2, bottom: 2 })

                  Text(this.getStatusLabel(share.status))
                    .fontSize(12)
                    .fontColor(share.status === 'active' ? '#4CAF50' : '#999999')
                    .margin({ left: 8 })
                }
                .margin({ top: 4 })

                if (share.targetName) {
                  Text(`分享给:${share.targetName}`)
                    .fontSize(12)
                    .fontColor('#999999')
                    .margin({ top: 2 })
                }
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)

              // 撤销按钮
              if (share.status === 'active' || share.status === 'pending') {
                Button('撤销')
                  .height(28)
                  .fontSize(12)
                  .fontColor('#F44336')
                  .backgroundColor('#FFEBEE')
                  .borderRadius(14)
                  .onClick(() => this.revokeShare(share.shareId))
              }
            }
            .width('100%')
            .padding(14)
            .backgroundColor('#FFFFFF')
            .borderRadius(10)
          }
        })
      }
      .width('100%')
      .layoutWeight(1)
      .padding({ left: 16, right: 16, top: 12 })

      // 操作按钮
      Row() {
        Button('+ 分享设备')
          .width('45%')
          .height(44)
          .backgroundColor('#007DFF')
          .fontColor('#FFFFFF')
          .borderRadius(22)
          .onClick(() => { this.showCreateDialog = true })

        Button('接受分享')
          .width('45%')
          .height(44)
          .backgroundColor('#F0F7FF')
          .fontColor('#007DFF')
          .borderRadius(22)
          .onClick(() => { this.showAcceptDialog = true })
      }
      .width('90%')
      .justifyContent(FlexAlign.SpaceBetween)
      .padding({ top: 12, bottom: 24 })
    }
    .width('100%')
    .height('100%')
  }

  // 收到的分享
  @Builder ReceivedSharesTab() {
    Column() {
      if (this.receivedShares.length === 0) {
        Column() {
          Text('暂无收到的分享')
            .fontSize(14).fontColor('#999999')
          Text('让朋友分享设备给你吧')
            .fontSize(12).fontColor('#CCCCCC').margin({ top: 4 })
        }
        .margin({ top: 100 })
        .alignItems(HorizontalAlign.Center)
      }

      List({ space: 8 }) {
        ForEach(this.receivedShares, (share: DeviceShare) => {
          ListItem() {
            Row() {
              Column() {
                Text(share.deviceName)
                  .fontSize(15).fontColor('#333333').fontWeight(FontWeight.Medium)
                Text(`来自:${share.ownerName}`)
                  .fontSize(12).fontColor('#999999').margin({ top: 2 })
                Text(`权限:${this.getPermissionLabel(share.permission)}`)
                  .fontSize(12)
                  .fontColor(this.getPermissionColor(share.permission))
                  .margin({ top: 2 })
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)

              // 去控制
              Button('控制')
                .height(28).fontSize(12)
                .fontColor('#007DFF').backgroundColor('#F0F7FF').borderRadius(14)
            }
            .width('100%')
            .padding(14)
            .backgroundColor('#FFFFFF')
            .borderRadius(10)
          }
        })
      }
      .width('100%')
      .layoutWeight(1)
      .padding({ left: 16, right: 16, top: 12 })
    }
    .width('100%')
    .height('100%')
  }

  // 创建分享对话框
  @Builder CreateShareDialog() {
    Column() {
      Text('分享设备')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      // 选择设备
      Text('选择设备').fontSize(14).fontColor('#666666').margin({ bottom: 8 })
      Row() {
        ForEach([
          { id: 'light-001', name: '客厅灯' },
          { id: 'ac-001', name: '空调' },
          { id: 'curtain-001', name: '窗帘' }
        ], (device: { id: string, name: string }) => {
          Text(device.name)
            .fontSize(13)
            .fontColor(this.selectedDevice === device.id ? '#FFFFFF' : '#333333')
            .backgroundColor(this.selectedDevice === device.id ? '#007DFF' : '#F5F5F5')
            .borderRadius(16)
            .padding({ left: 12, right: 12, top: 6, bottom: 6 })
            .margin({ right: 8 })
            .onClick(() => { this.selectedDevice = device.id })
        })
      }
      .margin({ bottom: 16 })

      // 选择权限
      Text('权限级别').fontSize(14).fontColor('#666666').margin({ bottom: 8 })
      Row() {
        ForEach([
          { key: 'view', label: '仅查看' },
          { key: 'control', label: '可控制' },
          { key: 'config', label: '可配置' }
        ], (item: { key: string, label: string }) => {
          Text(item.label)
            .fontSize(13)
            .fontColor(this.selectedPermission === item.key ? '#FFFFFF' : '#333333')
            .backgroundColor(this.selectedPermission === item.key ? '#007DFF' : '#F5F5F5')
            .borderRadius(16)
            .padding({ left: 12, right: 12, top: 6, bottom: 6 })
            .margin({ right: 8 })
            .onClick(() => { this.selectedPermission = item.key as SharePermission })
        })
      }
      .margin({ bottom: 16 })

      // 选择有效期
      Text('有效期').fontSize(14).fontColor('#666666').margin({ bottom: 8 })
      Row() {
        ForEach([
          { hours: 1, label: '1小时' },
          { hours: 24, label: '1天' },
          { hours: 168, label: '7天' },
          { hours: 720, label: '30天' }
        ], (item: { hours: number, label: string }) => {
          Text(item.label)
            .fontSize(13)
            .fontColor(this.selectedExpiry === item.hours ? '#FFFFFF' : '#333333')
            .backgroundColor(this.selectedExpiry === item.hours ? '#007DFF' : '#F5F5F5')
            .borderRadius(16)
            .padding({ left: 12, right: 12, top: 6, bottom: 6 })
            .margin({ right: 8 })
            .onClick(() => { this.selectedExpiry = item.hours })
        })
      }
      .margin({ bottom: 24 })

      // 按钮
      Row() {
        Button('取消')
          .width('45%').height(44)
          .backgroundColor('#F5F5F5').fontColor('#333333').borderRadius(22)
          .onClick(() => { this.showCreateDialog = false })
        Button('生成分享')
          .width('45%').height(44)
          .backgroundColor('#007DFF').fontColor('#FFFFFF').borderRadius(22)
          .onClick(() => this.createShare())
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .padding(24)
    .backgroundColor('#FFFFFF')
    .borderRadius({ topLeft: 24, topRight: 24 })
  }

  // 二维码面板
  @Builder QrCodePanel() {
    ShareQrCode({
      share: this.currentShare,
      onDismiss: () => { this.showQrCode = false }
    })
  }

  // 接受分享对话框
  @Builder AcceptShareDialog() {
    Column() {
      Text('接受分享')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      Text('请输入分享码').fontSize(14).fontColor('#666666').margin({ bottom: 8 })
      TextInput({ placeholder: '6位分享码', text: this.acceptCode })
        .fontSize(20)
        .textAlign(TextAlign.Center)
        .letterSpacing(4)
        .maxLength(6)
        .onChange((value: string) => { this.acceptCode = value.toUpperCase() })
        .margin({ bottom: 20 })

      Row() {
        Button('取消')
          .width('45%').height(44)
          .backgroundColor('#F5F5F5').fontColor('#333333').borderRadius(22)
          .onClick(() => { this.showAcceptDialog = false })
        Button('接受')
          .width('45%').height(44)
          .backgroundColor('#007DFF').fontColor('#FFFFFF').borderRadius(22)
          .onClick(() => this.acceptShare())
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .padding(24)
    .backgroundColor('#FFFFFF')
    .borderRadius({ topLeft: 24, topRight: 24 })
  }
}

踩坑与注意事项

1. 分享链接被转发

你把空调控制权分享给朋友A,A把链接转发给B。B也能控制你的空调?当然不行。

解法: 分享链接绑定使用次数(默认1次)。A使用后链接失效,B再点就打不开了。如果需要多人使用,创建时设置maxUseCount为更大的值。更安全的做法是指定目标账号,只有该账号可以使用。

2. 分享过期后的清理

分享到期后,权限自动收回。但对方App里可能还缓存着设备信息,他打开设备控制面板,发现控制不了——体验不好。

解法: 分享到期后,主动通知对方"您对XX设备的控制权限已过期"。同时清理对方App里的设备缓存。下次打开App时,该设备不再显示。

3. 撤销分享的实时性

你撤销了分享,但对方还在控制设备——因为撤销通知还没到对方App。

解法: 撤销分享时,通过MQTT推送撤销通知给对方App。对方App收到通知后,立即禁用控制面板。如果推送失败,对方下次控制时服务端会拒绝指令(因为分享记录已标记为revoked)。

4. 权限降级

你先给了朋友"管理"权限,后来想降为"控制"权限。怎么操作?

解法: 不支持权限降级,只能撤销后重新分享。权限降级涉及复杂的状态同步问题(对方可能正在使用管理功能),直接撤销重新创建更简单可靠。

5. 分享码的碰撞

6位分享码,36^6 ≈ 21亿种组合。碰撞概率极低,但不是零。如果两个分享码相同,用户输入一个码可能接受到错误的分享。

解法: 生成分享码时检查是否已存在。如果存在,重新生成。分享码的有效期短(最长30天),过期后码可以复用,所以碰撞概率更低。

HarmonyOS 6适配说明

HarmonyOS 6对设备分享做了几项增强:

  1. 深度链接分享:新增@ohos.app.ability.LinkService,支持通过深度链接打开App并自动接受分享。用户点击分享链接后,直接跳转到App的设备控制页面,不需要手动输入分享码。

  2. NFC碰一碰分享:两台手机碰一碰,自动完成设备分享。不需要分享码和二维码,体验更自然。

  3. 分享权限模板:新增预置的权限模板——“访客模板”(仅查看+1小时)、“朋友模板”(控制+24小时)、“租客模板”(控制+30天),用户选择模板一键创建分享。

  4. 分享审计日志:新增分享操作审计API,记录每次分享的创建、接受、使用、撤销操作,方便追溯。

适配代码示例:

// HarmonyOS 6 深度链接分享
import { AbilityConstant, Want } from '@kit.AbilityKit'

// 在UIAbility中处理深度链接
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  if (want.uri?.startsWith('smarthome://share')) {
    // 从URI中提取分享码
    const uri = new URL(want.uri)
    const code = uri.searchParams.get('code')
    
    if (code) {
      // 自动接受分享
      DeviceShareService.acceptShare(code).then(share => {
        if (share) {
          // 跳转到设备控制页面
        }
      })
    }
  }
}

总结

设备分享是智能家居的"临时钥匙"。家庭共享解决长期关系,设备分享解决临时授权。朋友来做客、保姆来打扫、租客来入住——不同的人需要不同的权限,设备分享让权限管理精细化。

权限粒度是关键。查看、控制、配置、管理四级权限,覆盖了所有场景。分享有效期和撤销机制保证安全,分享码和二维码让分享方便。

维度 评价
学习难度 ⭐⭐⭐ 权限模型不难,但分享安全、过期清理、实时撤销需要仔细设计
使用频率 ⭐⭐⭐ 设备分享不是高频功能,但在特定场景下非常实用
重要程度 ⭐⭐⭐⭐ 设备分享是智能家居社交化的基础,没有分享就没有协作

一句话:设备分享不是"把账号密码告诉别人",而是"给对的人对的权限,用完自动收回"。 这才是安全的分享。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。