端侧云存储头像上传实现 本文介绍了如何在应用端侧调用云存储服务搭建头像上传功能。云存储作为免维护的云端服务,提供安全可靠的文件存储能力,支持HTTPS加密传输、断点续传等特性。

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

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

云存储提供了客户端和服务端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');
  }
  }

二、课程总结

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

posted @ 2025-07-29 12:28  yfceshi  阅读(17)  评论(0)    收藏  举报