HarmonyOSNext 端云一体化(5) 
上一章节我们主要讲解了查询条件-谓词的基本使用技巧。这一章我们主要来讲解下客户端操作云存储。
云存储介绍 
云储存就是提供了一个可以存储物理文件的云端环境,比如存储图片、视频、音乐等,同时提供了的客户端操作云存储、云函数操作云存储的能力。我们这里主要讲解客户端操作存储,后续会讲解云函数操作云存储。
云存储的计费策略 
免费配额 
开通云存储服务后,华为供了免费额度以供试用,具体的配额明细如下。
| 计费项 | 详细说明 | 免费配额 | 
|---|---|---|
| 存储 | 存储数据的容量,以小时为统计周期,UTC时间整点结算,单位为GB。 | 5GB注意“存储”为按月计费,而非一次性计费。如果您使用的存储容量每月都超过免费配额,您每月都需支付相应的超额费用。例如,本月您使用了6GB存储容量,则本月您需支付1GB的超额费用。如果下个月您服务的存储容量为7GB,下个月您仍需支付2GB的超额费用。 | 
| 网络出站流量 | 公网流出流量,即通过互联网从云存储下载数据产生的流量。 | 1GB/天 | 
| 上传操作次数 | 上传接口请求次数。 | 20,000/天 | 
| 下载操作次数 | 下载接口请求次数。 | 50,000/天 | 
| 每个项目多个存储实例 | 单个项目支持创建多个存储实例。 | 免费档不支持此功能 | 
以某工具类APP为例,月新增下载量近1w,云存储提供的免费配额完全能支撑APP日常的调用。
升级到按量付费档 
当统计周期内的免费配额即将用尽时,您可以选择升级到按量付费档,以继续使用服务。或者,您也可以等到下个统计周期再使用云存储服务,在此之前服务将不再可用。
云存储按量付费价格如下表所示,套餐升级操作请参见升级到付费档。
| 计费项 | 详细说明 | 按量付费价格 | 
|---|---|---|
| 存储 | 存储数据的容量,以小时为统计周期,UTC时间整点结算,单位为GB。 | CNY 0.1679/GB | 
| 网络出站流量 | 公网流出流量,即通过互联网从云存储下载数据产生的流量。 | CNY 0.7751/GB | 
| 上传操作次数 | 上传接口请求次数。 | CNY 0.323/10,000 | 
| 下载操作次数 | 下载接口请求次数。 | CNY 0.0258/10,000 | 
| 每个项目多个存储实例 | 单个项目支持创建多个存储实例。 | 按量付费档支持此功能 | 
云存储核心功能 
客户端操作存储的核心功能主要有以下。
- 上传文件到云端
- 查看云端文件列表
- 查看云端文件元数据
- 设置云端文件数据
- 获取云端文件下载地址
- 下载云端文件到本地
- 删除云端文件
接下来我们便开始对云存储进行操作。
准备环境 
开通云存储 
我们需要提前在AGC平台上开通云存储环境。

我们可以看到,这个的云存储的实例名称为 default-bucket-xxxx
初始化云存储实例 
因为后期要操作云存储都需要用到云存储实例。所以需要初始化好。
使用默认云存储实例 
 bucket: cloudStorage.StorageBucket = cloudStorage.bucket(); // 获取默认的存储实例指定云存储实例 
 bucket: cloudStorage.StorageBucket = cloudStorage.bucket("default-bucket-xxxx"); // 获取默认的存储实例上传文件到云端 
上传文件到云端只能调用StorageBucket.uploadFile方法,但是该方法要求上传的文件路径必须存放在context.cacheDir目录下。因
此需要先提前做好这个处理。
步骤:
- 选择待上传的文件,下方示例代码中使用photoAccessHelper.PhotoViewPicker指定需要上传的文件。
- 将待上传的文件复制到context.cacheDir目录下。
- 调用StorageBucket.uploadFile接口创建上传任务,监听上传任务的progress、completed、failed等事件。
- 启动上传任务。
uploadFile在上传文件时,还支持上传自定义的标准的http头部信息。具体可以查看API说明
示例代码:
fn8 = async () => {
    // 使用photoAccessHelper选择指定的文件
    let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
    // 设置媒体文件类型为图像
    photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; 
    // 设置选择媒体文件的最大数目为1
    photoSelectOptions.maxSelectNumber = 1; 
    let photoViewPicker = new photoAccessHelper.PhotoViewPicker();
    // 调用select方法选择图片,并处理选择结果
    photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
      // 获取选中文件的URI
      let fileUri = photoSelectResult.photoUris[0];
      console.info(`pick file ${fileUri}`);
      // 提取文件名
      let fileName = fileUri.split('/').pop() as string;
      console.info(`file name ${fileName}`);
      // 创建缓存文件名,以当前时间戳和原文件名组合
      let cacheFile = `${Date.now()}_${fileName}`;
      console.info(`cacheFile ${cacheFile}`);
      // 拼接缓存文件路径
      let cacheFilePath = getContext().cacheDir + '/' + cacheFile;
      // 将选中文件复制到缓存目录下,文件名为cacheFile
      try {
        // 打开源文件
        let srcFile = fs.openSync(fileUri);
        // 打开目标文件(创建或读写)
        let dstFile = fs.openSync(cacheFilePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
        // 复制文件内容
        fs.copyFileSync(srcFile.fd, dstFile.fd);
        // 关闭源文件
        fs.closeSync(srcFile);
        // 关闭目标文件
        fs.closeSync(dstFile);
      } catch (e) {
        console.info(`copy file failed ${e.message}`);
        return;
      }
      // 使用默认实例上传文件至云存储
      this.bucket.uploadFile(getContext(this), {
        // 本地文件路径,位于context.cacheDir目录下
        localPath: cacheFile, 
        // 云端存储路径
        cloudPath: fileName    
      }).then((task: request.agent.Task) => {
        // 监听任务进度
        task.on('progress', (progress) => {
          console.info(`on progress ${JSON.stringify(progress)}`);
        });
        // 监听任务完成
        task.on('completed', (progress) => {
          console.info(`on completed ${JSON.stringify(progress)}`);
        });
        // 监听任务失败
        task.on('failed', (progress) => {
          console.error(`on failed ${JSON.stringify(progress)}`);
        });
        // 监听任务响应
        task.on('response', (response) => {
          console.info(`on response ${JSON.stringify(response)}`);
        });
        // 启动任务,并处理启动结果
        task.start((err: BusinessError) => {
          if (err) {
            console.error(`Failed to start the uploadFile task, Code: ${err.code}, message: ${err.message}`);
          } else {
            console.info(`Succeeded in starting a uploadFile task.`);
          }
        });
      }).catch((err: BusinessError) => {
        console.error(`uploadFile failed, Code: ${err.code}, message: ${err.message}`);
      });
    })
}
      Button("计算8 上传文件到云端")
        .onClick(this.fn8)刷新AGC-中的云存储: 可以看到文件成功上传了。

查看云端文件列表 
如果想要获取云端文件列表,可以使用 StorageBucket.list API。
参数 
| 参数名 | 类型 | 必填 | 说明 | 
|---|---|---|---|
| cloudPath | string | 是 | 云侧文件路径。 | 
| options | ListOptions | 否 | 列举操作的相关参数。 | 
ListOptions
| 名称 | 类型 | 只读 | 可选 | 说明 | 
|---|---|---|---|---|
| maxResults | number | 否 | 是 | 列举文件的最大数量,取值范围1-1000,默认则列举所有文件。 | 
| pageMarker | string | 否 | 是 | 分页标识。 | 
返回内容 
| 名称 | 类型 | 只读 | 可选 | 说明 | 
|---|---|---|---|---|
| directories | string[] | 否 | 否 | 列举操作返回的云侧目录列表。 | 
| files | string[] | 否 | 否 | 列举操作返回的云侧文件列表。 | 
| pageMarker | string | 否 | 是 | 分页标识。 | 
示例代码 
  fn9 = async () => {
    try {
      const res = await this.bucket.list('') // 获取根据根路径
      // const res = await this.bucket.list('avatar/') // 获取 avatar/ 路径下的文件。 需要注意的是 如果你输入的路径是 ava ,那么 avatar也会被匹配到
      AlertDialog.show({ message: JSON.stringify(res, null, 2) })
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` })
    }
  }
  
    Button("计算9 获取云端文件列表")
    .onClick(this.fn9)得到结果 

查看云端文件元数据 
我们可以通过该StorageBucket.getMetadataAPI获取到文件名、文件大小、文件类型等常用属性,也包括用户自定义的文件属性。
参数 
| 参数名 | 类型 | 必填 | 说明 | 
|---|---|---|---|
| cloudPath | string | 是 | 云侧文件路径。 | 
返回值 
| 类型 | 说明 | 
|---|---|
| Promise<Metadata> | Promise对象,返回云侧文件的元数据信息。 | 
示例代码 
  fn10 = async () => {
    try {
      const res = await this.bucket.list('')
      const cloudFilePath = res.files[0] // 获取第一个文件 -  实际开发中,你需要明确该路径下一定存在文件
      const res2 = await this.bucket.getMetadata(cloudFilePath)
      AlertDialog.show({ message: JSON.stringify(res2, null, 2) })
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` })
    }
  }
  
Button("计算10 查看云端文件元数据")
.onClick(this.fn10)得到结果 

获取云端文件下载地址 
我们之前通过getMetadata获取到了云端文件的相关信息。但是如果该文件是图片,而我们想要使用Image显示该图片,那么还需要使用StorageBucket.getDownloadURL获取到该文件的下载地址。
参数 
| 参数名 | 类型 | 必填 | 说明 | 
|---|---|---|---|
| cloudPath | string | 是 | 云侧文件路径。 | 
返回值 
| 类型 | 说明 | 
|---|---|
| Promise<string> | Promise对象,返回云侧文件下载地址。 | 
示例代码 
  fn11 = async () => {
    try {
      const res = await this.bucket.list('')
      const cloudFilePath = res.files[0] // 获取第一个文件 -  实际开发中,你需要明确该路径下一定存在文件
      const res2 = await this.bucket.getDownloadURL(cloudFilePath)
      AlertDialog.show({ message: JSON.stringify(res2, null, 2) })
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` })
    }
  }
  
Button("计算11 获取云端文件下载地址")
.onClick(this.fn11)得到结果 

下载云端文件到本地 
利用 StorageBucket.downloadFile 可以将文件下载到 ontext.cacheDir 目录下。
参数 
| 参数名 | 类型 | 必填 | 说明 | 
|---|---|---|---|
| context | common.BaseContext | 是 | 应用上下文。 | 
| parameters | DownloadParams | 是 | 下载相关参数。 | 
DownloadParams
| 名称 | 类型 | 只读 | 可选 | 说明 | 
|---|---|---|---|---|
| localPath | string | 否 | 否 | 本地文件路径,根路径为cache目录。 | 
| cloudPath | string | 否 | 否 | 云侧文件路径。 | 
| mode | request.agent.Mode | 否 | 是 | 下载任务类型,前端任务在应用切换到后台一段时间后失败/暂停;后台任务不受影响。默认为BACKGROUND。BACKGROUND:后台任务。FOREGROUND:前端任务。 | 
| overwrite | boolean | 否 | 是 | 当本地文件已存在时,是否覆盖本地文件,默认false。true:覆盖本地文件。false:不覆盖,若存在同名文件则下载失败。 | 
| network | request.agent.Network | 否 | 是 | 下载任务的网络配置,网络不满足设置条件时,未执行的任务等待执行,执行中的任务失败/暂停。默认为ANY。ANY:不限网络类型。WIFI:无线网络。CELLULAR:蜂窝数据网络。 | 
返回值 
| 类型 | 说明 | 
|---|---|
| Promise<[Task]> | Promise对象,返回下载任务。 | 
示例代码 
  fn12 = async () => {
    try {
      const res = await this.bucket.list('')
      const cloudFilePath = res.files[0] // 获取第一个文件 -  实际开发中,你需要明确该路径下一定存在文件
      const task = await this.bucket.downloadFile(getContext(this), {
        localPath: Date.now().toString(), // 本地文件路径, 下载成功后,文件将会保存在context.cacheDir目录
        cloudPath: cloudFilePath  // 云侧文件路径
      })
      task.on('progress', (progress) => {
        console.info(`on progress ${JSON.stringify(progress)} `);
      });
      task.on('completed', (progress) => {
        console.info(`on completed ${JSON.stringify(progress)} `);
        AlertDialog.show({ message: JSON.stringify("下载完成", null, 2) })
      });
      task.on('failed', (progress) => {
        console.error(`on failed ${JSON.stringify(progress)} `);
      });
      task.on('response', (response) => {
        console.info(`on response ${JSON.stringify(response)} `);
      });
      task.start((err: BusinessError) => {
        if (err) {
          console.error(`Failed to start the downloadFile task, Code: ${err.code}, message: ${err.message}`);
        } else {
          console.info(`Succeeded in starting a downloadFile task. result: ${task.tid}`);
        }
      });
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` })
    }
  }
  Button("计算12 下载云端文件")
    .onClick(this.fn12)得到结果 

删除云端文件 
调用StorageBucket.deleteFile删除云侧的文件。
参数 
| 参数名 | 类型 | 必填 | 说明 | 
|---|---|---|---|
| cloudPath | string | 是 | 云侧文件路径。 | 
返回值 
| 类型 | 说明 | 
|---|---|
| Promise<void> | Promise对象。无返回结果的Promise对象。 | 
示例代码 
  fn13 = async () => {
    try {
      const res = await this.bucket.list('')
      const cloudFilePath = res.files[0] // 获取第一个文件 -  实际开发中,你需要明确该路径下一定存在文件
      await this.bucket.deleteFile(cloudFilePath)
      promptAction.showToast({ message: `${"成功"}` })
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` })
    }
  }得到结果 

总结 
本文详细介绍了HarmonyOS Next中云存储的基本使用方法。主要内容包括:
- 云存储基础 - 介绍了云存储的基本概念
- 详细说明了免费配额和计费策略
- 讲解了云存储的核心功能
 
- 环境准备 - 如何在AGC平台开通云存储
- 如何初始化云存储实例,包括默认实例和指定实例的使用
 
- 核心API使用 - 文件上传:使用uploadFile将本地文件上传至云端
- 文件列表:通过list获取云端文件列表
- 元数据获取:使用getMetadata查看文件的详细信息
- 下载地址:通过getDownloadURL获取文件的下载链接
- 文件下载:使用downloadFile将云端文件下载到本地
- 文件删除:通过deleteFile删除云端文件
 
- 文件上传:使用
- 注意事项 - 文件上传必须使用context.cacheDir目录
- 文件下载同样只能保存在context.cacheDir目录下
- 操作云存储时需要注意错误处理
 
- 文件上传必须使用
通过本文的学习,开发者可以掌握HarmonyOS Next中云存储的基本操作,为应用开发中的文件存储需求提供解决方案。
如果你兴趣想要了解更多的鸿蒙应用开发细节和最新资讯,欢迎在评论区留言或者私信或者看我个人信息,可以加入技术交流群。
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=huv4f5rpu4g