鸿蒙HarmonyOS实战开发知识:方舟数据管理—ArkData

167 篇文章 0 订阅
167 篇文章 1 订阅

功能介绍

ArkData (方舟数据管理)为开发者提供数据存储、数据管理和数据同步能力,比如联系人应用数据可以保存到数据库中,提供数据库的安全、可靠以及共享访问等管理机制,也支持与手表同步联系人信息。

  • 标准化数据定义:提供HarmonyOS跨应用、跨设备的统一数据类型标准,包含标准化数据类型和标准化数据结构。

  • 数据存储:提供通用数据持久化能力,根据数据特点,分为用户首选项、键值型数据库和关系型数据库。

  • 数据管理:提供高效的数据管理能力,包括权限管理、数据备份恢复、数据共享框架等。

  • 数据同步:提供跨设备数据同步能力,比如分布式对象支持内存对象跨设备共享能力,分布式数据库支持跨设备数据库访问能力。

应用创建的数据库,都保存到应用沙盒,当应用卸载时,数据库也会自动删除。

运作机制

数据管理模块包括用户首选项、键值型数据管理、关系型数据管理、分布式数据对象、跨应用数据管理和统一数据管理框架。Interface接口层提供标准JS API接口,定义这些部件接口描述,供开发者参考。Frameworks&System service层负责实现部件数据存储、同步功能,还有一些SQLite和其他子系统的依赖。

图1 数据管理架构图

  • 用户首选项(Preferences):提供了轻量级配置数据的持久化能力,并支持订阅数据变化的通知能力。不支持分布式同步,常用于保存应用配置信息、用户偏好设置等。

  • 键值型数据管理(KV-Store):提供了键值型数据库的读写、加密、手动备份以及订阅通知能力。应用需要使用键值型数据库的分布式能力时,KV-Store会将同步请求发送给DatamgrService由其完成跨设备数据同步。

  • 关系型数据管理(RelationalStore):提供了关系型数据库的增删改查、加密、手动备份以及订阅通知能力。应用需要使用关系型数据库的分布式能力时,RelationalStore部件会将同步请求发送给DatamgrService由其完成跨设备数据同步。

  • 分布式数据对象(DataObject):独立提供对象型结构数据的分布式能力。如果应用需要重启后仍获取之前的对象数据(包含跨设备应用和本设备应用),则使用数据管理服务(DatamgrService)的对象持久化能力,做暂时保存。

  • 跨应用数据管理(DataShare):提供了数据提供者provider、数据消费者consumer以及同设备跨应用数据交互的增、删、改、查以及订阅通知等能力。DataShare不与任何数据库绑定,可以对接关系型数据库、键值型数据库。如果开发C/C++应用甚至可以自行封装数据库。在提供标准的provider-consumer模式基础上,同时提供了静默数据访问能力,即不再拉起provider而是直接通过DatamgrService代理访问provider的数据(目前仅关系型数据库支持静默数据访问方式)。

  • 统一数据管理框架(UDMF):提供了数据跨应用、跨设备交互标准,定义了跨应用、跨设备数据交互过程中的数据语言,提升数据交互效率。提供安全、标准化数据流通通路,支持不同级别的数据访问权限与生命周期管理策略,实现高效的数据跨应用、跨设备共享。

  • 数据管理服务(DatamgrService):提供其它部件的同步及跨应用共享能力,包括RelationalStore和KV-Store跨设备同步,DataShare静默访问provider数据,暂存DataObject同步对象数据等。

标准化数据定义概述

设备、应用交互的核心在于数据的互通,高效的数据互通基础是共识。为了降低应用/业务数据交互成本,促进数据生态建设,统一数据管理框架(UDMF)提供了标准化数据定义作为统一的HarmonyOS数据语言,用于构建跨应用、跨设备的统一数据标准与交互共识。

UDMF标准化数据定义包括标准化数据类型和标准化数据结构。

标准化数据类型:主要针对同一种数据类型,提供统一定义,即标准数据类型描述符,定义了包括标识数据类型的ID、类型归属关系等相关信息,用于解决HarmonyOS系统中的类型模糊问题。一般用于过滤或者识别某一种数据类型的场景,比如文件预览、文件分享等。

标准化数据结构:主要针对部分标准化数据类型定义了统一的数据内容结构,并明确了对应的描述信息。应用间使用标准化数据结构进行数据交互后,将遵从统一的解析标准,可有效减少适配相关的工作量。一般用于跨应用跨设备间的数据交互,比如拖拽。
 

标准化数据类型

场景介绍

标准化数据类型(Uniform Type Descriptor,简称UTD)用于解决系统中的类型模糊问题,即针对同一种数据类型,存在不同的类型描述方式:MIME Type、文件扩展名等。例如描述jpg/jpeg类型图片时,可以使用image/jpeg、.jpg、.jpeg或image/picture等方式进行描述。

当相关类型的数据进行跨应用、跨设备传输时,目标端应用/设备需要进行多方面的适配,才能够对数据内容进行相关处理,且存在无法识别的情况。

标准化数据类型分为预置数据类型和应用自定义数据类型。并且支持从其他类型体系,如文件名后缀和MIME type转换为UTD标准类型。

针对标准化数据类型,典型的应用场景有:文件管理中的图片预览、系统分享等。

标准化数据类型的设计和分类原则

标准化数据类型按层级结构构建

基于MIME Type或文件后缀名进行类型区分,存在另一个不足:即扁平化的数据类型定义。

扁平/松散的类型定义难以描述不同类型间的兼容与继承关系,且在实际使用过程中,会增加应用处理数据类型时的开发复杂度。例如搜索场景,用户从精确地搜索动物相关的任意类型图片,进一步扩展到动物相关的任意图片、视频或音频资源。为了满足上述场景,我们需要在定义数据类型时,支持类型层级结构。

构建标准类型的层级结构,定义层级结构中的类型归属关系,能够帮助系统、应用实现数据类型的分层、分类管理。当用户进行数据分享或拖拽时,如果数据中同时包含图片、视频、音频等内容,系统/应用可以根据层级按需对分享内容进行整理,如分享了几张照片、几条视频或几个媒体资源文件等。

标准化数据类型的分类原则

UTD中定义的标准化数据类型在设计原则上按物理和逻辑分为两类。图中涉及的标准化数据类型可见UniformDataType。

  • 按物理分类的根节点为general.entity,用于描述类型的物理属性,比如文件、目录等,具体可见图1。

  • 按逻辑分类的根节点为general.object,用于描述类型的功能性特征,如图片、网页等,具体可见图2。

按照此分类原则,可以从两个维度对数据类型进行描述。如描述图片时,可以是一个图片对象,同时也可以是一个文件。

并非所有的格式都具有两个维度,如general.calendar,更多的注重calendar对象的功能性描述。

图1 物理标准化数据类型示意图

图2 逻辑标准化数据类型示意图

标准化数据类型的定义

标准化数据类型包含了标准化数据类型的标识ID、归属类型关系、简要描述等信息,具体可见TypeDescriptor属性,每个类型定义具体包含以下内容:

  • typeId: 定义标准化数据类型的ID,该ID具有唯一性。
  • belongingToTypes: 定义标准化数据类型的归属关系,即该标准化数据类型归属于哪个更高层级的类型,允许存在一个标准化数据类型归属于多个类型的情况。
  • description: 标准化数据类型的简要说明。
  • referenceURL: 标准化数据类型的参考链接URL,用于描述类型的详细信息。
  • iconFile: 标准化数据类型的默认图标文件路径,可能为空字符串(即没有默认图标),应用可以自行决定是否使用该默认图标。
  • filenameExtensions: 标准化数据类型所关联的文件名后缀列表。
  • mimeTypes: 标准化数据类型所关联的多用途互联网邮件扩展类型列表。

预置数据类型

基于常用的数据类型,预先定义了一部分标准数据类型描述符,即预置数据类型。如用于描述音频文件的“general.audio”,描述视频文件的“general.video”,更多预置数据类型参考UniformDataType。

应用自定义数据类型

由于预置标准数据类型无法穷举所有数据类型,在业务跨应用、跨设备交互过程中,会涉及到一些应用独有的数据类型,因此支持应用声明自定义数据类型。

应用自定义的数据类型可继承已有的标准类型,例如业务自定义的图片类型可以使用“com.company.x-image”作为自定义数据类型的标识。

业务可以将自定义数据类型注册到系统中,这样其他业务可以在需要时引用,进而实现生态内各应用自定义数据类型的共享与统一。

工作原理

基于标准类型的层级结构,业务声明自己支持的数据类型标识符时,需要声明该类型标识符的层级逻辑,例如业务自定义图片类型UTD标识符“com.company.x-image”,并归属到general.image类中。UTD会检验自定义类型标识符,确保归属关系中不出现环状结构。

应用安装时,UTD会读取应用中自定义的数据类型进行安装,校验自定义类型数据符合约束条件后,应用自定义数据类型将被安装到设备中。应用启动后能正常读取到应用自定义的数据类型。如果引用其他应用定义的自定义数据类型,需要在应用开发时一并写入自定义数据类型配置文件中。

约束限制

针对自定义的类型描述各字段,有以下相关要求和限制:

  • TypeId: 定义标准化数据类型的ID,该ID具有唯一性,由应用bundleName + 具体类型名组成,不可缺省,允许包含数字、大小写字母、-和.。

  • BelongingToTypes: 定义标准化数据类型的归属关系,即该标准化数据类型归属于哪个更高层级的类型,所属类型可以为多个,但是必须为已存在的数据

    类型(标准化数据类型预置类型或其他新增自定义数据类型),不能为应用自定义类型本身,不能为空,且与现有标准化数据类型、其他新增自定义数据类型不能形成环行依赖结构。

  • FilenameExtensions: 应用自定义标准化数据类型所关联的文件后缀。可以缺省;可以为多个,每个后缀为以.开头且长度不超过127的字符串。

  • MIMETypes: 应用自定义标准化数据类型所关联的web消息数据类型。可以缺省;可以为多个,每个类型为长度不超过127的字符串。

  • Description: 应用自定义标准化数据类型的简要说明。可以缺省;填写时,长度为不超过255的字符串。

  • ReferenceURL: 应用自定义标准化数据类型的参考链接URL,用于描述类型的详细信息。可以缺省;填写时,长度为不超过255的字符串。

开发步骤

下面以新增媒体类文件类型场景为例,说明如何自定义UTD标准化数据类型。

  1. 当前应用在entry\src\main\resources\rawfile\arkdata\utd\目录下新增utd.json5文件。

  2. 在当前应用的utd.json5配置文件内新增所需的自定义数据类型。

    {
         "UniformDataTypeDeclarations": [
             {
                 "TypeId": "com.example.myFirstHap.image",
                 "BelongingToTypes": ["general.image"],
                 "FilenameExtensions": [".myImage", ".khImage"],
                 "MIMETypes": ["application/myImage", "application/khImage"],
                 "Description": "My Image.",
                 "ReferenceURL": ""
             },
             {
                 "TypeId": "com.example.myFirstHap.audio",
                 "BelongingToTypes": ["general.audio"],
                 "FilenameExtensions": [".myAudio", ".khAudio"],
                 "MIMETypes": ["application/myAudio", "application/khAudio"],
                 "Description": "My audio.",
                 "ReferenceURL": ""
             },
             {
                 "TypeId": "com.example.myFirstHap.video",
                 "BelongingToTypes": ["general.video"],
                 "FilenameExtensions": [".myVideo", ".khVideo"],
                 "MIMETypes": ["application/myVideo", "application/khVideo"],
                 "Description": "My video.",
                 "ReferenceURL": ""
             }
         ]
    }
  3. 如果其他应用要直接使用当前应用内的自定义数据类型,需要在其应用的entry\src\main\resources\rawfile\arkdata\utd\目录下新增utd.json5文件。

    然后在utd.json5配置文件中进行以下声明:

    {
        "ReferenceUniformDataTypeDeclarations": [
             {
                 "TypeId": "com.example.myFirstHap.image",
                 "BelongingToTypes": ["general.image"],
                 "FilenameExtensions": [".myImage", ".khImage"],
                 "MIMETypes": ["application/myImage", "application/khImage"],
                 "Description": "My Image.",
                 "ReferenceURL": ""
             }
        ]
    }
  4. 其他应用也可以在DevEco Studio中创建utd.json5模板,在模板中引用当前应用内的自定义数据类型之后,基于已引用的自定义数据类型进行自定义。同时,DevEco Studio还会对配置文件中的字段进行格式校验,utd.json5配置文件示例如下:

    {
        "UniformDataTypeDeclarations": [
            {
                "TypeId": "com.example.mySecondHap.image",
                "BelongingToTypes": ["com.example.myFirstHap.image"],
                "FilenameExtensions": [".myImageEx", ".khImageEx"],
                "MIMETypes": ["application/my-ImageEx", "application/khImageEx"],
                "Description": "My Image extension.",
                "ReferenceURL": ""
            }
        ]
    }

接口说明

以下是UTD常用接口说明,对于预置数据类型和应用自定义数据类型同样适用

接口名称描述
UniformDataType标准化数据类型的枚举定义。此处不再展开列举各枚举。
belongsTo(type: string): boolean判断当前标准化数据类型是否归属于指定的标准化数据类型。
isLowerLevelType(type: string): boolean判断当前标准化数据类型是否是指定标准化数据类型的低层级类型。
isHigherLevelType(type: string): boolean判断当前标准化数据类型是否是指定标准化数据类型的高层级类型。
getUniformDataTypeByFilenameExtension(filenameExtension: string, belongsTo?: string): string根据给定的文件后缀名和所归属的标准化数据类型查询标准化数据类型的ID。
getUniformDataTypeByMIMEType(mimeType: string, belongsTo?: string): string根据给定的MIME类型和所归属的标准化数据类型查询标准化数据类型的ID。

如何查询媒体类文件归属类型

下面以媒体类文件的归属类型查询场景为例,说明如何使用UTD。

  1. 导入uniformTypeDescriptor模块。
  2. 可根据 “.mp3” 文件后缀查询对应UTD数据类型,并查询对应UTD数据类型的具体属性。
  3. 可根据 “audio/mp3” MIMEType查询对应UTD数据类型,并查询对应UTD数据类型的具体属性。
  4. 将上述步骤查询出来的数据类型进行比较,确认类型是否相等。
  5. 根据上述步骤中查询到的标准数据类型“general.mp3”与表示音频数据的已知标准数据类型“general.audio”做比较查询,确认是否存在归属关系。
// 1.导入模块
import { uniformTypeDescriptor } from '@kit.ArkData';

try {
  // 2.可根据 “.mp3” 文件后缀查询对应UTD数据类型,并查询对应UTD数据类型的具体属性
  let fileExtention = '.mp3';
  let typeId1 = uniformTypeDescriptor.getUniformDataTypeByFilenameExtension(fileExtention);
  let typeObj1 = uniformTypeDescriptor.getTypeDescriptor(typeId1);
  console.info('typeId:' + typeObj1.typeId);
  console.info('belongingToTypes:' + typeObj1.belongingToTypes);
  console.info('description:' + typeObj1.description);
  console.info('referenceURL:' + typeObj1.referenceURL);
  console.info('filenameExtensions:' + typeObj1.filenameExtensions);
  console.info('mimeTypes:' + typeObj1.mimeTypes);



  // 3.可根据 “audio/mp3” MIMEType查询对应UTD数据类型,并查询对应UTD数据类型的具体属性。
  let mineType = 'audio/mp3';
  let typeId2 = uniformTypeDescriptor.getUniformDataTypeByMIMEType(mineType);
  let typeObj2 = uniformTypeDescriptor.getTypeDescriptor(typeId2);
  console.info('typeId:' + typeObj2.typeId);
  console.info('belongingToTypes:' + typeObj2.belongingToTypes);
  console.info('description:' + typeObj2.description);
  console.info('filenameExtensions:' + typeObj2.filenameExtensions);
  console.info('mimeTypes:' + typeObj2.mimeTypes);



  // 4.将数据类型进行比较,确认是否同一种数据类型
  if (typeObj1 != null && typeObj2 != null) {
    let ret = typeObj1.equals(typeObj2);
    console.info('typeObj1 equals typeObj2, ret:' + ret);
  }

  // 5.将查询到的标准数据类型“general.mp3”与表示音频数据的已知标准数据类型“general.audio”做比较查询,确认是否存在归属关系。
  if (typeObj1 != null) {
    let ret = typeObj1.belongsTo('general.audio');
    console.info('belongsTo, ret:' + ret);
    let mediaTypeObj = uniformTypeDescriptor.getTypeDescriptor('general.media');
    ret = mediaTypeObj.isHigherLevelType('general.audio'); // 确认是否存在归属关系
    console.info('isHigherLevelType, ret:' + ret);
  }
} catch (err) {
  console.error('err message:' + err.message + ', err code:' + err.code);
}

如何通过文件后缀获取对应的MIMEType列表

下面以通过“.mp3”文件后缀获取对应的MIMEType列表为例,说明如何通过文件后缀获取对应的MIMEType列表。

  1. 导入uniformTypeDescriptor模块。
  2. 可根据 “.mp3” 文件后缀查询对应UTD数据类型。
  3. 根据UTD数据类型查询对应的MIMEType列表。
// 1.导入模块
import { uniformTypeDescriptor } from '@kit.ArkData';
try {
  // 2.可根据 “.mp3” 文件后缀查询对应UTD数据类型。
  let fileExtention = '.mp3';
  let typeId = uniformTypeDescriptor.getUniformDataTypeByFilenameExtension(fileExtention);
  // 3.根据UTD数据类型查询对应的MIMEType列表。
  let typeObj = uniformTypeDescriptor.getTypeDescriptor(typeId);
  let mimeTypes = typeObj.mimeTypes;
  console.info('mimeTypes:' + mimeTypes);
} catch (err) {
  console.error('err message:' + err.message + ', err code:' + err.code);
}

如何通过MIMEType获取对应的后缀列表

下面以通过“audio/mp3”MIMEType获取对应文件后缀列表为例,说明如何通过MIMEType获取对应的后缀列表。

  1. 导入uniformTypeDescriptor模块。
  2. 可根据 “audio/mp3” MIMEType查询对应UTD数据类型。
  3. 根据UTD数据类型查询对应的MIMEType列表。
// 1.导入模块
import { uniformTypeDescriptor } from '@kit.ArkData';
try {
  // 2.可根据 “audio/mp3” MIMEType查询对应UTD数据类型。
  let mineType = 'audio/mp3';
  let typeId = uniformTypeDescriptor.getUniformDataTypeByMIMEType(mineType);
  // 3. 根据UTD数据类型查询对应的MIMEType列表
  let typeObj = uniformTypeDescriptor.getTypeDescriptor(typeId);
  let filenameExtensions = typeObj.filenameExtensions;
  console.info('filenameExtensions:' + filenameExtensions);
} catch (err) {
  console.error('err message:' + err.message + ', err code:' + err.code);
}

标准化数据结构

场景介绍

针对一些UTD标准化数据类型,为了方便业务使用,我们提供了标准化数据结构,例如系统定义的桌面卡片类型(对应的标准化数据类型标识为为'openharmony.form'),我们明确定义了该数据结构对应的相关描述信息。

某些业务场景下应用可以直接使用我们具体定义的UTD标准化数据结构,例如跨应用拖拽场景。拖出方应用可以按照标准化数据结构将拖拽数据写入拖拽事件,拖入方应用从拖拽事件中读取拖拽数据并按照标准化数据结构进行数据的解析。这使得不同应用间的数据交互遵从相同的标准定义,有效减少了跨应用数据交互的开发工作量。

当前支持的标准化数据结构

UDMF针对部分标准化数据类型定义的标准化数据结构如下所示:

数据类型数据结构说明
'general.text'Text文本
'general.plain-text'PlainText纯文本
'general.hyperlink'Hyperlink超链接
'general.html'HTML富文本
'general.file'File文件
'general.image'Image图片
'general.video'Video视频
'general.audio'Audio音频
'general.folder'Folder文件夹
'openharmony.form'SystemDefinedForm卡片
'openharmony.app-item'SystemDefinedAppItem图标
'openharmony.pixel-map'SystemDefinedPixelMap二进制图片

约束限制

UDMF支持批量数据记录的分组管理,每个分组整体大小不超过200MB,其中PlainText、Hyperlink、HTML内单个属性值数据上限20M。

接口说明

UDMF提供了统一数据对象UnifiedData,用于封装一组数据记录UnifiedRecord。数据记录UnifiedRecord则是对UDMF支持的数据内容的抽象定义,例如一条文本记录、一条图片记录等。数据记录中的数据内容类型对应为各数据类型UniformDataType。

以下是标准化数据结构的常用接口说明

类名称接口名称描述
UnifiedRecordgetType(): string获取当前数据记录对应的具体数据类型。
UnifiedDataconstructor(record: UnifiedRecord)用于创建带有一条数据记录的统一数据对象。
UnifiedDataaddRecord(record: UnifiedRecord): void在当前统一数据对象中添加一条数据记录。
UnifiedDatagetRecords(): Array<UnifiedRecord>将当前统一数据对象中的所有数据记录取出,通过本接口取出的数据为UnifiedRecord类型,需通过getType获取数据类型后转为子类再使用。

开发步骤

以使用标准化数据结构定义数据内容(包含图片、纯文本两条数据记录)为例,提供基本的开发步骤。

  1. 导入unifiedDataChannel和uniformTypeDescriptor模块。

    import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
  2. 创建图片数据记录,并初始化得到带有该数据记录的UnifiedData对象。

    • 创建图片数据记录。

      let image = new unifiedDataChannel.Image();
    • 修改对象属性。

      // Image对象包含一个属性imageUri
      image.imageUri = '...';
    • 访问对象属性。

      console.info(`imageUri = ${image.imageUri}`);
    • 创建一个统一数据对象实例。

      let unifiedData = new unifiedDataChannel.UnifiedData(image);
  3. 创建纯文本数据类型记录,将其添加到刚才创建的UnifiedData对象。

    let plainText = new unifiedDataChannel.PlainText();
    plainText.textContent = 'this is textContent of plainText';
    plainText.abstract = 'abstract of plainText';
    plainText.details = {
      plainKey1: 'plainValue1',
      plainKey2: 'plainValue2',
    };
    unifiedData.addRecord(plainText);
  4. 记录添加完成后,可获取当前UnifiedData对象内的所有数据记录。

    let records = unifiedData.getRecords();
  5. 遍历每条记录,判断该记录的数据类型,转换为子类对象,得到原数据记录。

for (let i = 0; i < records.length; i ++) {
  // 读取该数据记录的类型
  let type = records[i].getType();
  switch (type) {
    case uniformTypeDescriptor.UniformDataType.IMAGE:
      // 转换得到原图片数据记录
      let image = records[i] as unifiedDataChannel.Image;
      break;
    case uniformTypeDescriptor.UniformDataType.PLAIN_TEXT:
      // 转换得到原文本数据记录
      let plainText = records[i] as unifiedDataChannel.PlainText;
      break;
    default:
      break;
  }
}

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习资料+学习PDF文档

HarmonyOS Next 最新全套视频教程

  纯血版鸿蒙全套学习资料(面试、文档、全套视频等)              

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

  • 9
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值