HarmonyOS之深入解析应用的权限
【摘要】
一、权限概述
① 基本概念
应用沙盒:
系统利用内核保护机制来识别和隔离应用资源,可将不同的应用隔离开,保护应用自身和系统免受恶意应用的攻击。默认情况下,应用间不能彼此交互,而且对系统的访问会受...
一、权限概述
① 基本概念
- 应用沙盒:
-
- 系统利用内核保护机制来识别和隔离应用资源,可将不同的应用隔离开,保护应用自身和系统免受恶意应用的攻击。默认情况下,应用间不能彼此交互,而且对系统的访问会受到限制。
-
- 例如,如果应用 A(一个单独的应用)尝试在没有权限的情况下读取应用B的数据或者调用系统的能力拨打电话,操作系统会阻止此类行为,因为应用 A 没有被授予相应的权限。
- 应用权限:
-
- 由于系统通过沙盒机制管理各个应用,在默认规则下,应用只能访问有限的系统资源。但应用为了扩展功能的需要,需要访问自身沙盒之外的系统或其他应用的数据(包括用户个人数据)或能力;系统或应用也必须以明确的方式对外提供接口来共享其数据或能力。为了保证这些数据或能力不会被不当或恶意使用,就需要有一种访问控制机制来保护,这就是应用权限。
-
- 应用权限是程序访问操作某种对象的许可。权限在应用层面要求明确定义且经用户授权,以便系统化地规范各类应用程序的行为准则与权限许可。
- 权限保护的对象:
-
- 权限保护的对象可以分为数据和能力。
-
- 数据包含了个人数据(如照片、通讯录、日历、位置等)、设备数据(如设备标识、相机、麦克风等)、应用数据。
-
- 能力包括了设备能力(如打电话、发短信、联网等)、应用能力(如弹出悬浮框、创建快捷方式等)等。
- 权限开放范围
-
- 权限开放范围指一个权限能被哪些应用申请。
-
- 按可信程度从高到低的顺序,不同权限开放范围对应的应用可分为:系统服务、系统应用、系统预置特权应用、同签名应用、系统预置普通应用、持有权限证书的后装应用、其他普通应用,开放范围依次扩大。
- 敏感权限:
-
- 涉及访问个人数据(如:照片、通讯录、日历、本机号码、短信等)和操作敏感能力(如:相机、麦克风等)的权限。
- 应用核心功能:
-
- 一个应用可能提供了多种功能,其中应用为满足用户的关键需求而提供的功能,称为应用的核心功能。这是一个相对宽泛的概念,本规范用来辅助描述用户权限授权的预期。用户选择安装一个应用,通常是被应用的核心功能所吸引。
-
- 比如导航类应用,定位导航就是这种应用的核心功能;比如媒体类应用,播放以及媒体资源管理就是核心功能,这些功能所需要的权限,用户在安装时内心已经倾向于授予(否则就不会去安装)。
-
- 与核心功能相对应的是辅助功能,这些功能所需要的权限,需要向用户清晰说明目的、场景等信息,由用户授权。既不属于核心功能,也不是支撑核心功能的辅助功能,就是多余功能。不少应用存在并非为用户服务的功能,这些功能所需要的权限通常被用户禁止。
- 最小必要权限:
-
- 保障应用某一服务类型正常运行所需要的应用权限的最小集,一旦缺少将导致该类型服务无法实现或无法正常运行的应用权限。
② 运作机制
- 系统所有应用均在应用沙盒内运行。默认情况下,应用只能访问有限的系统资源,系统负责管理应用对资源的访问权限。这些限制是通过 DAC(Discretionary Access Control)、MAC(Mandatory Access Control)以及本文描述的应用权限机制等多种不同的形式实现的。
- 因应用需要实现其某些功能而必须访问系统或其他应用的数据或操作某些器件,此时就需要系统或其他应用能提供接口,考虑到安全,就需要对这些接口采用一种限制措施,这就是称为“应用权限”的安全机制。
- 接口的提供涉及到其权限的命名和分组、对外开放的范围、被授予的应用、以及用户的参与和体验。应用权限管理模块的目的就是负责管理由接口提供方(访问客体)、接口使用方(访问主体)、系统(包括云侧和端侧)和用户等共同参与的整个流程,保证受限接口是在约定好的规则下被正常使用,避免接口被滥用而导致用户、应用和设备受损。
③ 权限声明
- 应用需要在 config.json 中使用“reqPermissions”属性对需要的权限逐个进行声明。
- 若使用到的三方库也涉及权限使用,也需统一在应用的 config.json 中逐个声明。
- 没有在 config.json 中声明的权限,应用就无法获得此权限的授权。
④ 动态申请敏感权限
- 动态申请敏感权限基于用户可知可控的原则,需要应用在运行时主动调用系统动态申请权限的接口,系统弹框由用户授权,用户结合应用运行场景的上下文,识别出应用申请相应敏感权限的合理性,从而做出正确的选择。
- 即使用户向应用授予了请求的权限,应用在调用受此权限管控的接口前,也应该先检查自己有无此权限,而不能把之前授予的状态持久化,因为用户在动态授予后还可以通过设置取消应用的权限。
⑤ 自定义权限
- HarmonyOS 为了保证应用对外提供的接口不被恶意调用,需要对调用接口的调用者进行鉴权。
- 大多情况下,系统已定义的权限满足了应用的基本需要,若有特殊的访问控制需要,应用可在 config.json 中以"defPermissions": []属性来定义新的权限,并通过“availableScope”和“grantMode”两个属性分别确定权限的开放范围和授权方式,使得权限定义更加灵活且易于理解。
- 为了避免应用自定义新权限出现重名的情况,建议应用对新权限的命名以包名的前两个字段开头,这样可以防止不同开发者的应用间出现自定义权限重名的情况。
⑥ 权限保护方法
- 保护 Ability:通过在 config.json 里对应的Ability中配置"permissions": [“权限名”]属性,即可实现保护整个 Ability 的目的,无指定权限的应用不能访问此 Ability。
- 保护 API:若 Ability 对外提供的数据或能力有多种,且开放范围或保护级别也不同,可以针对不同的数据或能力在接口代码实现中通过 verifyPermission(String permissionName, int pid, int uid)来对 uid 标识的调用者进行鉴权。
⑦ 权限的限制
- 同一应用自定义权限个数不能超过 1024 个。
- 同一应用申请权限个数不能超过 1024 个。
- 为避免与系统权限名冲突,应用自定义权限名不能以 ohos 开头,且权限名长度不能超过 256 字符。
- 自定义权限授予方式不能为 user_grant。
- 自定义权限开放范围不能为 restricted。
二、权限分类
- HarmonyOS 根据接口所涉数据的敏感程度或所涉能力的安全威胁影响,定义了不同开放范围与授权方式的权限来保护数据。
- 当前权限的开放范围分为:
-
- all:所有应用可用
-
- signature:平台签名应用可用
-
- privileged:预制特权应用可用
-
- restricted:证书可控应用可用
- 应用在使用对应服务的能力或数据时,需要申请对应权限:
-
- 已在 config.json 文件中声明的非敏感权限,会在应用安装时自动授予,该类权限的授权方式为系统授权(system_grant)。
-
- 敏感权限需要应用动态申请,通过运行时发送弹窗的方式请求用户授权,该类权限的授权方式为用户授权(user_grant)。
- 当应用调用服务时,服务会对应用进行权限检查,如果没有对应权限则无法使用该服务。
① 敏感权限
- 敏感权限的申请需要按照动态申请流程向用户申请授权。
- 敏感权限说明:
权限分类名称 | 权限名 | 说明 |
---|---|---|
位置 | ohos.permission.LOCATION | 允许应用在前台运行时获取位置信息,如果应用在后台运行时也要获取位置信息,则需要同时申请ohos.permission.LOCATION_IN_BACKGROUND权限 |
ohos.permission.LOCATION_IN_BACKGROUND | 允许应用在后台运行时获取位置信息,需要同时申请ohos.permission.LOCATION权限 | |
相机 | ohos.permission.CAMERA | 允许应用使用相机拍摄照片和录制视频 |
麦克风 | ohos.permission.MICROPHONE | 允许应用使用麦克风进行录音 |
日历 | ohos.permission.READ_CALENDAR | 允许应用读取日历信息 |
ohos.permission.WRITE_CALENDAR | 允许应用在设备上添加、移除或修改日历活动 | |
健身运动 | ohos.permission.ACTIVITY_MOTION | 允许应用读取用户当前的运动状态 |
健康 | ohos.permission.READ_HEALTH_DATA | 允许应用读取用户的健康数据 |
分布式数据管理 | ohos.permission.DISTRIBUTED_DATASYNC | 允许不同设备间的数据交换 |
ohos.permission.DISTRIBUTED_DATA | 允许应用使用分布式数据的能力 | |
媒体 | ohos.permission.MEDIA_LOCATION | 允许应用访问用户媒体文件中的地理位置信息 |
ohos.permission.READ_MEDIA | 允许应用读取用户外部存储中的媒体文件信息 | |
ohos.permission.WRITE_MEDIA | 允许应用读写用户外部存储中的媒体文件信息 |
② 非敏感权限
- 非敏感权限不涉及用户的敏感数据或危险操作,仅需在 config.json 中声明,应用安装后即被授权。
- 非敏感权限说明:
权限名 | 说明 |
---|---|
ohos.permission.GET_NETWORK_INFO | 允许应用获取数据网络信息 |
ohos.permission.GET_WIFI_INFO | 允许获取WLAN信息 |
ohos.permission.USE_BLUETOOTH | 允许应用查看蓝牙的配置 |
ohos.permission.DISCOVER_BLUETOOTH | 允许应用配置本地蓝牙,并允许其查找远端设备且与之配对连接 |
ohos.permission.SET_NETWORK_INFO | 允许应用控制数据网络 |
ohos.permission.SET_WIFI_INFO | 允许配置WLAN设备 |
ohos.permission.SPREAD_STATUS_BAR | 允许应用以缩略图方式呈现在状态栏 |
ohos.permission.INTERNET | 允许使用网络socket |
ohos.permission.MODIFY_AUDIO_SETTINGS | 允许应用程序修改音频设置 |
ohos.permission.RECEIVER_STARTUP_COMPLETED | 允许应用接收设备启动完成广播 |
ohos.permission.RUNNING_LOCK | 允许申请休眠运行锁,并执行相关操作 |
ohos.permission.ACCESS_BIOMETRIC | 允许应用使用生物识别能力进行身份认证 |
ohos.permission.RCV_NFC_TRANSACTION_EVENT | 允许应用接收卡模拟交易事件 |
ohos.permission.COMMONEVENT_STICKY | 允许发布粘性公共事件的权限 |
ohos.permission.SYSTEM_FLOAT_WINDOW | 提供显示悬浮窗的能力 |
ohos.permission.VIBRATE | 允许应用程序使用马达 |
ohos.permission.USE_TRUSTCIRCLE_MANAGER | 允许调用设备间认证能力 |
ohos.permission.USE_WHOLE_SCREEN | 允许通知携带一个全屏IntentAgent |
ohos.permission.SET_WALLPAPER | 允许设置静态壁纸 |
ohos.permission.SET_WALLPAPER_DIMENSION | 允许设置壁纸尺寸 |
ohos.permission.REARRANGE_MISSIONS | 允许调整任务栈 |
ohos.permission.CLEAN_BACKGROUND_PROCESSES | 允许根据包名清理相关后台进程 |
ohos.permission.KEEP_BACKGROUND_RUNNING | 允许Service Ability在后台继续运行 |
ohos.permission.GET_BUNDLE_INFO | 查询其他应用的信息 |
ohos.permission.ACCELEROMETER | 允许应用程序读取加速度传感器的数据 |
ohos.permission.GYROSCOPE | 允许应用程序读取陀螺仪传感器的数据 |
ohos.permission.MULTIMODAL_INTERACTIVE | 允许应用订阅语音或手势事件 |
ohos.permission.radio.ACCESS_FM_AM | 允许用户获取收音机相关服务 |
ohos.permission.NFC_TAG | 允许应用读写Tag卡片 |
ohos.permission.NFC_CARD_EMULATION | 允许应用实现卡模拟功能 |
ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE | 允许获取分布式组网内设备的状态变化 |
ohos.permission.GET_DISTRIBUTED_DEVICE_INFO | 允许获取分布式组网内的设备列表和设备信息 |
③ 受限开放的权限
- 受限开放的权限通常是不允许三方应用申请的。如果有特殊场景需要使用,请提供相关申请材料到应用市场申请相应权限证书。如果应用未申请相应的权限证书,却试图在 config.json 文件中声明此类权限,将会导致应用安装失败。
- 另外,由于此类权限涉及到用户敏感数据或危险操作,当应用申请到权限证书后,还需按照动态申请权限的流程向用户申请授权。
- 受限开放权限说明:
权限分类名称 | 典型场景 | 权限名 | 说明 |
---|---|---|---|
通讯录 | 社交、通讯、备份和恢复用户信息、电话拦截等 | ohos.permission.READ_CONTACTS | 允许应用读取联系人数据 |
通讯、备份和恢复用户信息等 | ohos.permission.WRITE_CONTACTS | 允许应用添加、移除和更改联系人数据 |
三、权限申请
- 需要在 config.json 文件中的“reqPermissions”字段中声明所需要的权限:
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "$string:permreason_camera",
"usedScene":
{
"ability": ["com.mycamera.Ability", "com.mycamera.AbilityBackground"],
"when": "always"
}
},{
...
}
]
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 权限申请格式采用数组格式,可支持同时申请多个权限,权限个数最多不能超过 1024 个。
- reqPermissions 权限申请字段说明:
键 | 值说明 | 类型 | 取值范围 | 默认值 | 规则约束 |
---|---|---|---|---|---|
name | 必须,填写需要使用的权限名称 | 字符串 | 自定义 | 无 | 未填写时,解析失败 |
reason | 可选,当申请的权限为user_grant权限时此字段必填 描述申请权限的原因 |
字符串 | 显示文字长度不能超过256个字节 | 空 | user_grant权限必填,否则不允许在应用市场上架 需做多语种适配 |
usedScene | 可选,当申请的权限为user_grant权限时此字段必填 描述权限使用的场景和时机。场景类型有:ability、when(调用时机)。可配置多个ability |
ability:字符串数组 when:字符串 |
ability:ability的名称 when:inuse(使用时)、always(始终) |
ability:空 when:inuse |
user_grant权限必填ability,可选填when |
- 如果声明使用的权限的 grantMode 是 system_grant,则权限会在当应用安装的时候被自动授予。
- 如果声明使用的权限的 grantMode 是 user_grant,则必须经用户手动授权(用户在弹框中授权或进入权限设置界面授权)才可使用。用户会看到 reason 字段中填写的理由,来帮助用户决定是否给予授权。
- 对于授权方式为 user_grant 的权限,每一次执行需要这一权限的操作时,都需要检查自身是否有该权限。当自身具有权限时,才可继续执行,否则应用需要请求用户授予权限。
四、自定义权限
- 需要在config.json文件中的“defPermissions”字段中自定义所需的权限:
{
"module": {
"defPermissions": [
{
"name": "com.myability.permission.MYPERMISSION",
"grantMode": "system_grant",
"availableScope": ["signature"]
}, {
...
}
]
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 权限定义格式采用数组格式,可支持同时定义多个权限,自定义的权限个数最多不能超过 1024 个。
- defPermissions 权限定义字段说明:
键 | 值说明 | 类型 | 取值范围 | 默认值 | 规则约束 |
---|---|---|---|---|---|
name | 必填,权限名称 为最大可能避免重名,采用反向域公司名+应用名+权限名组合 |
字符串 | 自定义 | 无 | 第三方应用不允许填写系统存在的权限,否则安装失败 未填写解析失败 权限名长度不能超过256个字符 |
grantMode | 必填,权限授予方式 | 字符串 | user_grant(用户授权) system_grant(系统授权) |
system_grant | 未填值或填写了取值范围以外的值时,自动赋予默认值 不允许第三方应用填写user_grant,填写后会自动赋予默认值 |
availableScope | 选填,权限限制范围 不填则表示此权限对所有应用开放 |
字符串数组 | signature privileged restricted |
空 | 填写取值范围以外的值时,权限限制范围不生效 由于第三方应用并不在restricted的范围内,很少会出现权限定义者不能访问自身定义的权限的情况,所以不允许三方应用填写restricted |
label | 选填,权限的简短描述,若未填写,则使用到简短描述的地方由权限名取代 | 字符串 | 自定义 | 空 | 需要多语种适配 |
description | 选填,权限的详细描述,若未填写,则使用到详细描述的地方由label取代 | 字符串 | 自定义 | 空 | 需要多语种适配 |
- 权限授予方式字段说明:
授予方式(grantMode) | 说明 | 自定义权限是否可指定该级别 | 取值样例 |
---|---|---|---|
system_grant | 在“config.json”里面声明,安装后系统自动授予 | 是 | GET_NETWORK_INFO 、GET_WIFI_INFO |
user_grant | 在“config.json”里面声明,并在使用时动态申请,用户授权后才可使用 | 否,如自定义则强制修改为system_grant | CAMERA、MICROPHONE |
- 权限限制范围字段说明:
权限范围(availableScope) | 说明 | 自定义权限是否可指定该级别 | 取值样例 |
---|---|---|---|
restricted | 需要开发者申请对应证书后才能被使用的特殊权限 | 否 | WRITE_CONTACTS、READ_CONTACTS |
signature | 权限定义方和使用方的签名一致。需在“config.json”里面声明后,由权限管理模块负责签名校验一致后,可使用 | 是 | 对应用(或Ability)操作的系统接口上由系统定义权限以及应用自定义的权限 如:find某Ability,连接某Ability |
privileged | 预置在系统版本中的特权应用可申请的权限 | 是 | SET_TIME、MANAGE_USER_STORAGE |
五、访问权限控制
① Ability 的访问权限控制
- 在 config.json 中填写“abilities”的“permissions”字段,即只有拥有该权限的应用可访问此Ability。
- 下面的例子表明只有拥有“ohos.permission.CAMERA”权限的应用可以访问此 ability:
"abilities": [
{
"name": ".MainAbility",
"description": "$string:description_main_ability",
"icon": "$media:hiworld",
"label": "HiCamera",
"launchType": "standard",
"orientation": "portrait",
"visible": false,
"permissions": [
"ohos.permission.CAMERA"
],
}
]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 权限保护字段说明:
键 | 值说明 | 类型 | 取值范围 | 默认值 | 规则约束 |
---|---|---|---|---|---|
permissions | 选填,权限名称。用以表示此ability受哪个权限保护,即只有拥有此权限的应用可访问此ability | 字符串 | 自定义 | 无 | 目前仅支持填写一个权限名,若填写多个权限名,仅第一个权限名称有效 |
② Ability 接口的访问权限控制
- 在 Ability 实现中,如需要对特定接口对调用者做访问控制,可在服务侧的接口实现中,主动通过 verifyCallingPermission、verifyCallingOrSelfPermission 来检查访问者是否拥有所需要的权限。
if (verifyCallingPermission("ohos.permission.CAMERA") != IBundleManager.PERMISSION_GRANTED) {
// 调用者无权限,做错误处理
}
// 调用者权限校验通过,开始提供服务
- 1
- 2
- 3
- 4
六、API 接口说明
接口原型 | 接口详细描述 |
---|---|
int verifyPermission(String permissionName, int pid, int uid) | 接口功能:查询指定PID、UID的应用是否已被授予某权限 输入参数:permissionName:权限名;pid:进程id; uid:uid 输出参数:无 返回值: IBundleManager.PERMISSION_DENIED、IBundleManager.PERMISSION_GRANTED |
int verifyCallingPermission(String permissionName) | 接口功能:查询IPC跨进程调用方的进程是否已被授予某权限 输入参数:permissionName:权限名 输出参数:无 返回值: IBundleManager.PERMISSION_DENIED、IBundleManager.PERMISSION_GRANTED |
int verifySelfPermission(String permissionName) | 接口功能:查询自身进程是否已被授予某权限 输入参数:permissionName:权限名 输出参数:无 返回值: IBundleManager.PERMISSION_DENIED、IBundleManager.PERMISSION_GRANTED |
int verifyCallingOrSelfPermission(String permissionName) | 接口功能:当有远端调用检查远端是否有权限,否则检查自身是否拥有权限 输入参数:permissionName:权限名 输出参数:无 返回值: IBundleManager.PERMISSION_DENIED、IBundleManager.PERMISSION_GRANTED |
boolean canRequestPermission(String permissionName) | 接口功能:向系统权限管理模块查询某权限是否不再弹框授权了 输入参数:permissionName:权限名 输出参数:无 返回值:true允许弹框,false不允许弹框 |
void requestPermissionsFromUser (String[] permissions, int requestCode) | 接口功能:向系统权限管理模块申请权限(接口可支持一次申请多个。若下一步操作涉及到多个敏感权限,可以这么用,其他情况建议不要这么用。因为弹框还是按权限组一个个去弹框,耗时比较长。用到哪个权限就去申请哪个) 输入参数: permissions:权限名列表;requestCode: 请求应答会带回此编码以匹配本次申请的权限请求 输出参数:无 返回值:无 |
void onRequestPermissionsFromUserResult (int requestCode, String[] permissions, int[] grantResults) | 接口功能:调用requestPermissionsFromUser后的应答接口 输入参数:requestCode:requestPermission中传入的requestCode;permissions:申请的权限名; grantResults:申请权限的结果 输出参数:无 返回值:无 |
七、动态申请权限
- 在 config.json 文件中声明所需要的权限:
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "$string:permreason_camera",
"usedScene": {
"ability": ["com.mycamera.Ability", "com.mycamera.AbilityBackground"],
"when": "always"}
}, {
...
}
]
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 使用 ohos.app.Context.verifySelfPermission 接口查询应用是否已被授予该权限:
-
- 如果已被授予权限,可以结束权限申请流程;
-
- 如果未被授予权限,继续执行下一步。
- 使用 canRequestPermission 查询是否可动态申请:
-
- 如果不可动态申请,说明已被用户或系统永久禁止授权,可以结束权限申请流程;
-
- 如果可动态申请,使用 requestPermissionFromUser 动态申请权限。
if (verifySelfPermission("ohos.permission.CAMERA") != IBundleManager.PERMISSION_GRANTED) {
// 应用未被授予权限
if (canRequestPermission("ohos.permission.CAMERA")) {
// 是否可以申请弹框授权(首次申请或者用户未选择禁止且不再提示)
requestPermissionsFromUser(
new String[] { "ohos.permission.CAMERA" } , MY_PERMISSIONS_REQUEST_CAMERA);
} else {
// 显示应用需要权限的理由,提示用户进入设置授权
}
} else {
// 权限已被授予
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 通过重写 ohos.aafwk.ability.Ability 的回调函数 onRequestPermissionsFromUserResult 接收授予结果:
@Override
public void onRequestPermissionsFromUserResult (int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_CAMERA: {
// 匹配requestPermissions的requestCode
if (grantResults.length > 0
&& grantResults[0] == IBundleManager.PERMISSION_GRANTED) {
// 权限被授予
// 注意:因时间差导致接口权限检查时有无权限,所以对那些因无权限而抛异常的接口进行异常捕获处理
} else {
// 权限被拒绝
}
return;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
八、完整示例
文章来源: blog.csdn.net,作者:Serendipity·y,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/Forever_wj/article/details/119108038
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)