一、端侧调用云存储上传头像

云存储是一种可伸缩、免维护的云端存储服务,可用于存储图片、音频、视频或其他由用户生成的内容。借助云存储服务,您可以无需关心存储服务器的开发、部署、运维、扩容等事务,大大降低了应用使用存储的门槛,让您可以专注于应用的业务能力构建,助力您的商业成功。

云存储提供了客户端和服务端SDK,您可以使用云存储SDK为您的应用实现安全可靠的文件上传和下载服务,同时具备如下优势。

  • 安全可靠:全流程使用HTTPS协议对用户的传输数据进行加密保护,并采用安全的加密协议将文件加密存储在云端。

  • 断点续传:因网络原因或用户原因导致的操作中止,只需要简单地传入操作中止的位置,就可以尝试重新开始该操作。

  • 可伸缩:提供EB级的数据存储,解决海量数据存储的难题。

  • 易维护:通过简单的判断返回异常就可以定位出错误原因,定位快捷方便。

云存储模块提供使用云存储对文件进行上传、下载、查询和删除等操作能力。以上传宝宝头像为例,端侧调用云存储需要以下操作步骤:

1)引入云存储模块

import {cloudStorage} from '@kit.CloudFoundataionKit';
import { BusinessError, request } from '@kit.BasicServicesKit';

2)初始化云存储实例

const bucket: cloudStorage.StorageBucket = cloudStorage.bucket();

3)调用云存储上传接口,上传图片

bucket.uploadFile(getContext(this), {
  localPath: cacheFilePath,
  cloudPath: cloudPath
})

4)调用云存储获取图片地址接口

bucket.getDownloadURL(cloudPath).then((downloadURL: string) => {
  this.imageUrl = downloadURL;
})

完整代码:

import { cloudFunction, cloudDatabase, cloudStorage } from '@kit.CloudFoundationKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
import { SymbolGlyphModifier } from '@kit.ArkUI';
​
import { Feeding } from '../model/Feeding';
import { fileIo } from '@kit.CoreFileKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
​
const bucket: cloudStorage.StorageBucket = cloudStorage.bucket();
type UploadCompleteCallback = (uploadSuccess: boolean) => void;
​
interface BabyAge {
  years: number;
  months: number;
  days: number;
  totalDays: number;
}
​
interface ResponseBody {
  code: number;
  desc: string;
  data: BabyAge
}
​
@Entry
@Component
struct Index {
​
  controller: SearchController = new SearchController();
  @State birthday: string = "";
  @State callFunctionResult: BabyAge | undefined = undefined;
​
  currentDateTime: Date = new Date();
  condition: cloudDatabase.DatabaseQuery<Feeding> = new cloudDatabase.DatabaseQuery(Feeding);
  private databaseZone = cloudDatabase.zone("default");
​
  @State allFeedingList: Feeding[] = [];
​
  @State feeding: Feeding = new Feeding();
  @State isFeeding: boolean = false;
  @State startTime: Date = new Date();
​
  @State imageUrl: string | ResourceStr = $r('app.media.empty_image');
​
  // 查询当前喂养记录信息
  async queryAllFeeding() {
    try {
      // 构建查询条件
      const queryCondition = this.condition
        .greaterThanOrEqualTo("startTime", this.currentDateTime.setHours(0, 0, 0, 0))
        .and()
        .lessThanOrEqualTo("startTime", this.currentDateTime.setHours(23, 59, 59, 999));
      const result = await this.databaseZone.query(queryCondition);
      if (result.length > 0) {
        this.allFeedingList = result;
      }
    } catch (error) {
      // 异常处理
      this.allFeedingList = [];
    }
  }
  // 新增喂养记录
  async insertFeeding(feeding: Feeding) {
    await this.databaseZone.upsert(feeding);
    await this.queryAllFeeding();
  }
​
  // 删除数据
  async deleteFeeding(feeding: Feeding) {
    try {
      await this.databaseZone.delete(feeding);
      await this.queryAllFeeding();
    } catch (error) {
      const err: BusinessError = error;
      this.getUIContext().getPromptAction().showToast({
        message: err.message
      })
    }
  }
​
  async aboutToAppear(): Promise<void> {
    this.queryAllFeeding();
  }
​
  build() {
    Column({ space: 10 }) {
      Text("请先设置宝宝出生日期")
        .fontColor(Color.Grey)
        .height(54)
      Search({ controller: this.controller, value: this.birthday })
        .width('90%')
        .height('54vp')
        .searchIcon(
          new SymbolGlyphModifier($r('sys.symbol.calendar_badge_play'))
            .fontColor([Color.Grey])
            .fontSize('30fp')
        )
        .cancelButton({
          style: CancelButtonStyle.INVISIBLE
        })
        .borderRadius('8vp')
        .onClick(() => {
          CalendarPickerDialog.show({
            selected: new Date(this.birthday),
            acceptButtonStyle: {
              style: ButtonStyleMode.EMPHASIZED
            },
            cancelButtonStyle: {
              fontColor: Color.Grey
            },
            onAccept: async (value) => {
              this.birthday = value.toLocaleDateString();
              console.info("calendar onAccept:" + JSON.stringify(value))
              let result: cloudFunction.FunctionResult = await cloudFunction.call({
                name: 'calculate-baby-age',
                version: '$latest',
                timeout: 10 * 1000,
                data: {
                  birthday: this.birthday
                }
              });
              let body = result.result as ResponseBody;
              this.callFunctionResult = body.data;
            }
          })
        })
      if (this.callFunctionResult !== undefined) {
        Row() {
          Column({ space: 8 }) {
            Image(this.imageUrl)
              .width(64)
              .height(64)
              .borderRadius(16)
              .onClick(() => {
                this.upLoadImage();
              })
            Text(`我已经${this.callFunctionResult.years}岁了 ${this.callFunctionResult.months}月 ${this.callFunctionResult.days}天了~`)
            Text(`我已经出生${this.callFunctionResult.totalDays}天了~`)
          }
          .width('100%')
        }
        .width('90%')
​
        Button(`${this.isFeeding ? '停止喂养' : '开始喂养'}`)
          .backgroundColor(this.isFeeding ? Color.Orange : Color.Green)
          .onClick(async () => {
            this.isFeeding = !this.isFeeding;
            if (this.isFeeding) {
              this.startTime = new Date();
              this.feeding.id = this.allFeedingList.length + 1;
              this.feeding.startTime = this.startTime;
            } else {
              this.feeding.totalDuration = new Date().getTime() - this.startTime.getTime();
              await this.insertFeeding(this.feeding);
            }
          })
​
        if (this.allFeedingList.length > 0) {
          List() {
            ForEach(this.allFeedingList, (item: Feeding) => {
              ListItem() {
                Column({ space: 8 }) {
                  Row() {
                    Text(`${item.type}喂养`)
                    Text(`${item.startTime.toLocaleDateString()}`)
                  }
                  .width('100%')
                  .height(32)
                  .justifyContent(FlexAlign.SpaceBetween)
                  Text(`总喂养时长:${item.totalDuration >= (60 * 1000) ? (item.totalDuration / (60 * 1000)) + 'm' : (item.totalDuration / 1000) + 's'}`)
                    .width('100%')
                    .height(32)
                  Row() {
                    Button("删除")
                      .onClick(async () => {
                        this.getUIContext().getPromptAction().showDialog({
                          title: '温馨提示',
                          message: '确定要删除该喂养记录吗?',
                          buttons: [
                            {
                              text: '取消',
                              color: '#D3D3D3'
                            },
                            {
                              text: '确定',
                              color: '#FF5277'
                            }
                          ],
                        })
                          .then(async data => {
                            console.info('showDialog success, click button: ' + data.index);
                            if (data.index === 1) {
                              await this.deleteFeeding(item);
                            }
                          })
                          .catch((err: Error) => {
                            console.info('showDialog error: ' + err);
                          })
                      })
                  }
                  .width('100%')
                }
                .backgroundColor(0xf2f2f2)
                .padding(12)
                .borderRadius(8)
              }
            })
          }
          .width('90%')
          .height(450)
          .divider({
            strokeWidth: 2,
            color: Color.White
          })
        }
      }
    }.width('100%').height('100%')
  }
​
  private upLoadImage() {
    this.selectImage().then((selectImageUri: string) => {
      if (!selectImageUri) {
        return;
      }
      this.initStates();
      // copy select file to cache directory
      const fileName = selectImageUri.split('/').pop() as string;
      const cacheFilePath = `${getContext().cacheDir}/${fileName}`;
      this.copyFile(selectImageUri, cacheFilePath);
​
      const cloudPath = `default-bucket-lg41j/image_${new Date().getTime()}.jpg`;
      bucket.uploadFile(getContext(this), {
        localPath: cacheFilePath,
        cloudPath: cloudPath
      }).then(task => {
        // add task event listener
        this.addEventListener(task, this.onUploadCompleted(cloudPath, cacheFilePath));
        // start task
        task.start().catch((err: BusinessError) => {
        });
      }).catch((err: BusinessError) => {
​
      });
    }).catch((err: Error) => {
    });
  }
​
  private async selectImage(): Promise<string> {
    return new Promise((resolve: (selectUri: string) => void, reject: (err: Error) => void) => {
      const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
      // 过滤选择媒体文件类型为IMAGE
      photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
      photoSelectOptions.maxSelectNumber = 1;
      new photoAccessHelper.PhotoViewPicker().select(photoSelectOptions)
        .then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
          resolve(photoSelectResult.photoUris[0]);
        })
        .catch((err: Error) => {
          reject(err);
        });
    });
  }
​
  private addEventListener(task: request.agent.Task, completeCallback: UploadCompleteCallback) {
    task.on('progress', (progress) => {
​
    });
    task.on('completed', (progress) => {
      completeCallback(true);
    });
    task.on('response', (response) => {
​
    });
    task.on('failed', (progress) => {
      completeCallback(false);
    });
  }
​
  private onUploadCompleted(cloudPath: string, cacheFilePath: string) {
    return (uploadSuccess: boolean) => {
      if (uploadSuccess) {
        bucket.getDownloadURL(cloudPath).then((downloadURL: string) => {
          this.imageUrl = downloadURL;
        }).catch((err: BusinessError) => {
        });
      }
      // delete cache file when task finished
      fileIo.unlink(cacheFilePath).catch((err: BusinessError) => {
      });
    };
  }
​
  private copyFile(src: string, dest: string) {
    const srcFile = fileIo.openSync(src);
    const dstFile = fileIo.openSync(dest, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    fileIo.copyFileSync(srcFile.fd, dstFile.fd);
    fileIo.closeSync(srcFile);
    fileIo.closeSync(dstFile);
  }
​
  private initStates() {
    this.imageUrl = $r('app.media.empty_image');
  }
}

二、课程总结

通过十小节课程的学习,相信大家已经完全掌握了端云一体化开发流程,能够独立完成云函数开发、调试、部署、以及调用,独立完成云数据库设计、开发、部署、以及调用,以及云存储的端侧调用。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐