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也能控制你的空调——这显然不行。
解法:
- 绑定账号:分享时指定对方账号,只有该账号可以使用
- 使用次数限制:分享链接只能使用一次,使用后失效
- IP绑定:分享链接只能在特定IP段使用(如你家WiFi下)
- 二次确认:对方使用分享链接时,需要主人再次确认
代码实战
基础用法:设备分享服务
设备分享的创建、验证、权限检查、撤销。
// 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对设备分享做了几项增强:
-
深度链接分享:新增
@ohos.app.ability.LinkService,支持通过深度链接打开App并自动接受分享。用户点击分享链接后,直接跳转到App的设备控制页面,不需要手动输入分享码。 -
NFC碰一碰分享:两台手机碰一碰,自动完成设备分享。不需要分享码和二维码,体验更自然。
-
分享权限模板:新增预置的权限模板——“访客模板”(仅查看+1小时)、“朋友模板”(控制+24小时)、“租客模板”(控制+30天),用户选择模板一键创建分享。
-
分享审计日志:新增分享操作审计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) {
// 跳转到设备控制页面
}
})
}
}
}
总结
设备分享是智能家居的"临时钥匙"。家庭共享解决长期关系,设备分享解决临时授权。朋友来做客、保姆来打扫、租客来入住——不同的人需要不同的权限,设备分享让权限管理精细化。
权限粒度是关键。查看、控制、配置、管理四级权限,覆盖了所有场景。分享有效期和撤销机制保证安全,分享码和二维码让分享方便。
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐ 权限模型不难,但分享安全、过期清理、实时撤销需要仔细设计 |
| 使用频率 | ⭐⭐⭐ 设备分享不是高频功能,但在特定场景下非常实用 |
| 重要程度 | ⭐⭐⭐⭐ 设备分享是智能家居社交化的基础,没有分享就没有协作 |
一句话:设备分享不是"把账号密码告诉别人",而是"给对的人对的权限,用完自动收回"。 这才是安全的分享。
- 点赞
- 收藏
- 关注作者
评论(0)