鸿蒙 HarmonyOS NEXT端云一体化开发-云存储篇

本文涉及的产品
多模态交互后付费免费试用,全链路、全Agent
简介: 本文介绍用户登录后获取昵称、头像的方法,包括通过云端API和AppStorage两种方式,并实现上传头像至云存储及更新用户信息。同时解决图片缓存问题,添加上传进度提示,支持自动登录判断,提升用户体验。

一、获取昵称和头像

TODO:用户通过邮箱登录后跳转到用户信息页,用户信息页展示用户头像和用户昵称信息,用户头像和用户昵称在用户信息页中均可修改,在点击下方的更新信息按钮后,新的用户信息将重新传递到云端。

1. 方法一:云端API
// 获取用户信息的两种方法:
cloud.auto().getCruuentUser()
2. 方法二:AppStorage
// 方法二:在用户登录时,将返回的用户信息存储在AppStorage中,在用户信息页中在取出展示。
// 登录页主要代码:
 const result = await cloud.auth().signIn({
         credentialInfo: {
           kind: 'email',
           email: this.phone,
           verifyCode: this.verifCode
         }
       })
const user = result.getUser()
 AppStorage.setOrCreate("user",user) // 存
// 用户信息页:
// 注意 Authuse 和null是不兼容的,所以需要联合类型
 @StorageLink("user")  user :AuthUser |null = null
  aboutToAppear(): void {
    this.UserName =  this.user?.getDisplayName()? this.user?.getDisplayName():""
    this.UserImgUrl = this.user?.getPhotoUrl()?this.user?.getPhotoUrl():""
  }
3. 示例

TODO:用户通过邮箱登录后,获取到用户昵称和头像

  1. 登录页:
import cloud from '@hw-agconnect/cloud';
import { Auth, VerifyCodeAction } from '@hw-agconnect/cloud';
import router from '@ohos.router';
@Entry
@Component
struct PageTest {
  @State verificationBtnStr:string= "验证码"
  @State phone:string = ""
  @State verifcationBtnStatus:boolean = true
  @State timer :number = 0
  @State countDown:number = 60
  @State data:string = ""
  @State verifCode:string = ""
  // 注销
  loginOut(){
    cloud.auth().signOut().then(() => {
      //登出成功
      AlertDialog.show({
        title: "提示",
        message: "注销用户成功",
      })
    }).catch(() => {
      //登出失败
    });
  }
  //登录
  async login(){
    
    
     this.data = this.verifCode
     try{
       const result = await cloud.auth().signIn({
         credentialInfo: {
           kind: 'email',
           email: this.phone,
           verifyCode: this.verifCode
         }
       })
       const user = result.getUser()
       AppStorage.setOrCreate("user",user)
       router.replaceUrl({url:'pages/UserInfo'})
     } catch (e) {
       this.data= JSON.stringify(e)
       AlertDialog.show({
         title: "提示",
         message: JSON.stringify(e),
       })
     }
     
  }
  // 云端获取
  getCloudQrCode(){
    cloud.auth().requestVerifyCode({
      action: VerifyCodeAction.REGISTER_LOGIN,
      lang: 'zh_CN',
      sendInterval: 60,
      verifyCodeType: {
        email: this.phone,
        kind: "email",
      }
    }).then(verifyCodeResult => {
      //验证码申请成功
      console.log(JSON.stringify(verifyCodeResult))
      this.data = JSON.stringify(verifyCodeResult)
      AlertDialog.show({
        title: "提示",
        message: "获取验证码成功",
      })
    }).catch((error: Promise<Result>) => {
      AlertDialog.show({
        title: "提示",
        message: "获取验证码失败",
      })
      //验证码申请失败
    });
  }
  // 初始化参数:
  initData(){
    clearInterval(this.timer)
    this.verifcationBtnStatus = true
    this.verificationBtnStr  = "验证码"
    this.countDown  = 60
  }
  // 发送验证码
  getCode(){
    if(this.phone==''){
      AlertDialog.show({
        title: "提示",
        message: "请输入用户邮箱",
      })
      return;
    }
    this.verifcationBtnStatus = false
    this.getCloudQrCode()
    this.timer  = setInterval(()=>{
      this.verificationBtnStr = `${this.countDown}s`
      if(this.countDown===0){
        this.initData()
        return;
      }
      this.countDown --
    },1000)
  }
  build() {
    Column({space:20}){
      TextInput({placeholder:'请输入用户邮箱:'}).width("100%").height(60).margin({top:20})
        .onChange((value)=>{
          this.phone = value
        })
      Row(){
        TextInput({placeholder:"请输入验证码"}).layoutWeight(1).margin({right:20})
          .onChange((value)=>{
            this.verifCode =value
          })
        Button(this.verificationBtnStr).width(120).onClick(()=>{
          this.getCode()
        }).enabled(this.verifcationBtnStatus)
      }.width("100%").height(60)
      Button("登录").onClick( ()=>{
        this.data = "1aaaaaa"
        this.login()
      }).width("100%").height(40).padding({
        left:50,right:50
      }).backgroundColor("#ff08be4b")
      Button("注销").onClick( ()=>{
        this.data = "1aaaaaa"
        this.loginOut()
      }).width("100%").height(40).padding({
        left:50,right:50
      }).backgroundColor("#ff08be4b")
      Text(this.data).width("100%").height(200).backgroundColor(Color.Pink)
    }.width("100%").height("100%").padding({left:10,right:10})
  }
}

  1. 用户信息页:
import { AuthUser } from '@hw-agconnect/cloud'
@Entry
@Component
struct UserInfo {
  @State message: string = 'Hello World'
  @State UserImgUrl:string = 'app.media.user_dark'
  @State UserName:string = 'test'
  // 注意 Authuse 和null是不兼容的,所以需要联合类型
  @StorageLink("user")  user :AuthUser |null = null
  aboutToAppear(): void {
    this.UserName =  this.user?.getDisplayName()? this.user?.getDisplayName():""
    this.UserImgUrl = this.user?.getPhotoUrl()?this.user?.getPhotoUrl():""
  }
 
  build() {
      Column({space:20}){
        Image(this.UserImgUrl?this.UserImgUrl:$r("app.media.user_dark")).width(100).height(100).margin({top:20})
          .onClick(()=>{
            this.UserImgUrl = "https://img0.baidu.com/it/u=4218602670,1294229692&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500"
          })
        TextInput({placeholder:"请设置昵称",text:this.UserName}).width("100%").height(40).margin({left:20,right:20})
          .onChange((value)=>{
            this.UserName = value
          })
        Text(this.UserImgUrl).width("100%").height(200)
      
      }.width("100%").height("100%")
  }
}

二、云存储服务

本项目在创建项目时就开通了云存储业务,所以就不演示了。

注意点1:免费额度为:5G(超过则需要付费)

注意点2:如果是需要点击开通该服务的,在操作完开通流程后,记得更新agconnect-services.json文件

三、云存储上传文件

TODO:用户登录后,通过点击头像打开相册,将相册中的图片上传到云存储中,并更新用户信息。:

// 示例代码
 // 步骤1:打开相册
const options = new picker.PhotoSelectOptions()
options.MIMEType= picker.PhotoViewMIMETypes.IMAGE_TYPE // img类型
options.maxSelectNumber=1 // 选择图片数量
const result = await new picker.PhotoViewPicker().select(options)
//步骤2:上传
await cloud.storage().upload({
  localPath:result.photoUris[0],
  cloudPath:`images/${this.user?.getUid()}.jpg`
})
//步骤3:回显
const url = await cloud.storage().getDownloadURL(`images/${this.user?.getUid()}.jpg`)
this.UserImgUrl = url // 赋值给UserImgUrl显示到用户信息页上

完整示例:

  1. 用户信息页:
import cloud, { AuthUser } from '@hw-agconnect/cloud'
import { picker } from '@kit.CoreFileKit'
@Entry
@Component
struct UserInfo {
  @State message: string = 'Hello World'
  @State UserImgUrl:string = 'app.media.user_dark'
  @State UserName:string = 'test'
  // 注意 Authuse 和null是不兼容的,所以需要联合类型
  @StorageLink("user")  user :AuthUser |null = null
  aboutToAppear(): void {
    this.UserName =  this.user?.getDisplayName()? this.user?.getDisplayName():""
    this.UserImgUrl = this.user?.getPhotoUrl()?this.user?.getPhotoUrl():""
  }
  async updateInfo(){
    await this.user?.updateProfile({
      displayName:this.UserName,
      photoUrl:this.UserImgUrl
    })
    AlertDialog.show({
      title: "提示",
      message: "用户信息更新成功",
    })
  }
  build() {
      Column({space:20}){
        Image(this.UserImgUrl?this.UserImgUrl:$r("app.media.user_dark")).width(100).height(100).margin({top:20})
          .onClick(async ()=>{
            // this.UserImgUrl = "https://img0.baidu.com/it/u=4218602670,1294229692&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500"
            try{
              // 打开相册
              const options = new picker.PhotoSelectOptions()
              options.MIMEType= picker.PhotoViewMIMETypes.IMAGE_TYPE // img类型
              options.maxSelectNumber=1 // 选择图片数量
              const result = await new picker.PhotoViewPicker().select(options)
              // 上传
              await cloud.storage().upload({
                localPath:result.photoUris[0],
                cloudPath:`images/${this.user?.getUid()}.jpg`
              })
              // 回显
              const url = await cloud.storage().getDownloadURL(`images/${this.user?.getUid()}.jpg`)
              this.UserImgUrl = url
            } catch (e) {
            }
          })
        TextInput({placeholder:"请设置昵称",text:this.UserName}).width("100%").height(40).margin({left:20,right:20})
          .onChange((value)=>{
            this.UserName = value
          })
        Text(this.UserImgUrl).width("100%").height(200)
        Button("更新信息")
          .onClick(()=>{
            this.updateInfo()
          })
      }.width("100%").height("100%")
  }
}
  1. 效果

四、图片缓存问题

1. 问题说明:
因为本次实例是以登录用户的UUID作为图片的名称上传的,所以如果切换图片上传的话,
新的图片地址和旧的图片地址都是一样的,系统就会认为是同一张图片,从而只显示系统中存在
图片,而并不会重新更新云存储中新的图片。
2. 解决方法:
// 在每次上传成功后,重新更新存储图片的RUL即可。
// 方法1:可以修改上传时的图片名称
// 方法2:在url后添加一个随机的参数
// 方法1:`images/${this.user?.getUid()}${new Date().getTime().toString()}.jpg`
// 打开相册
const options = new picker.PhotoSelectOptions()
options.MIMEType= picker.PhotoViewMIMETypes.IMAGE_TYPE // img类型
options.maxSelectNumber=1 // 选择图片数量
const result = await new picker.PhotoViewPicker().select(options)
// 上传
const imgName = `images/${this.user?.getUid()}${new Date().getTime().toString()}.jpg`
await cloud.storage().upload({
  localPath:result.photoUris[0],
  cloudPath:imgName
})
// 回显
const url = await cloud.storage().getDownloadURL(imgName)
this.UserImgUrl = url

五:初始化页面判断(自动登录)

TODO:在应用的登录界面,初始化Auth实例,获取AGC的用户信息,检查是否有已经登录的用户。如果有,则可以直接进入用户界面,否则显示登录界面。

// 在生命周期方法内判断,如果存在user信息就直接跳转到用户信息页中
aboutToAppear(): void {
    cloud.auth().getCurrentUser().then(user=>{
      if(user){
        //业务逻辑
        AppStorage.setOrCreate("user",user)
        router.replaceUrl({url:'pages/UserInfo'})
      }
    });
  }

六:上传进度功能实现

TODO:点击头像框时,跳转到图库中,选择需要的头像并点击确定后,返回到用户信息页中,加载文件上传的进度,到100%时,头像上传成功,并显示选择的图片。
// 使用层叠布局将上传进度显示在头像框上
// 通过头像框是否可用判断是否显示进度框
import cloud, { AuthUser } from '@hw-agconnect/cloud'
import { picker } from '@kit.CoreFileKit'
@Entry
@Component
struct UserInfo {
  @State message: string = 'Hello World'
  @State UserImgUrl:string = 'app.media.user_dark'
  @State UserName:string = 'test'
  @State loadingText:string = "0" // 上传进度
   @State UserImgEnable:boolean = true // 头像是否可用
  // 注意 Authuse 和null是不兼容的,所以需要联合类型
  @StorageLink("user")  user :AuthUser |null = null
  aboutToAppear(): void {
    this.UserName =  this.user?.getDisplayName()? this.user?.getDisplayName():""
    this.UserImgUrl = this.user?.getPhotoUrl()?this.user?.getPhotoUrl():""
  }
  async updateInfo(){
    await this.user?.updateProfile({
      displayName:this.UserName,
      photoUrl:this.UserImgUrl
    })
    AlertDialog.show({
      title: "提示",
      message: "用户信息更新成功",
    })
  }
  build() {
      Column({space:20}){
        Stack(){
          Image(this.UserImgUrl?this.UserImgUrl:$r("app.media.user_dark")).width(100).height(100).borderRadius(100)
            .enabled(this.UserImgEnable)
            .onClick(async ()=>{
              try{
                // 打开相册
                const options = new picker.PhotoSelectOptions()
                options.MIMEType= picker.PhotoViewMIMETypes.IMAGE_TYPE // img类型
                options.maxSelectNumber=1 // 选择图片数量
                const result = await new picker.PhotoViewPicker().select(options)
                // 上传
                const imgName = `images/${this.user?.getUid()}${new Date().getTime().toString()}.jpg`
                await cloud.storage().upload({
                  localPath:result.photoUris[0],
                  cloudPath:imgName,
                  onUploadProgress:(value)=>{
                    this.UserImgEnable = false
                     this.loadingText =  Math.floor(100* value.loaded/value.total).toString()
                  }
                })
                this.UserImgEnable = true
                // 回显
                const url = await cloud.storage().getDownloadURL(imgName)
                this.UserImgUrl = url
              } catch (e) {
              }
            })
        // 判断头像框可用状态
         if(!this.UserImgEnable){
           Text(`${this.loadingText}%`).width(100).height(100).borderRadius(100).backgroundColor(Color.Black).opacity(.5)
             .fontWeight(700)
             .fontSize(24)
             .fontColor(Color.Black)
             .textAlign(TextAlign.Center)
         }
        }.margin({top:20})
        TextInput({placeholder:"请设置昵称",text:this.UserName}).width("100%").height(40).margin({left:20,right:20})
          .onChange((value)=>{
            this.UserName = value
          })
        Text(this.UserImgUrl).width("100%").height(200)
        Button("更新信息")
          .onClick(()=>{
            this.updateInfo()
          })
      }.width("100%").height("100%")
  }
}
  • 效果图:

目录
相关文章
|
20天前
|
存储 负载均衡 数据库
鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇
本文介绍基于华为AGC的端云一体化开发流程,涵盖项目创建、云函数开通、应用配置及DevEco集成。重点讲解云函数的编写、部署、调用与传参,并涉及环境变量设置、负载均衡、重试机制与熔断策略等高阶特性,助力开发者高效构建稳定云端服务。
192 0
鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇
|
9天前
|
传感器 监控 安全
HarmonyOS NEXT 5.0 的星闪(NearLink)开发应用案例
V哥分享HarmonyOS NEXT 5.0星闪开发实战,涵盖智能车钥匙无感解锁与工业传感器监控。低延迟、高可靠,代码完整,速来学习!
|
4月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:外卖App
仓颉语言实战分享,教你如何用仓颉开发外卖App界面。内容包括页面布局、导航栏自定义、搜索框实现、列表模块构建等,附完整代码示例。轻松掌握Scroll、List等组件使用技巧,提升HarmonyOS应用开发能力。
|
3月前
|
安全 JavaScript API
鸿蒙开发核心要素
鸿蒙开发核心要素
|
4月前
|
存储 IDE 定位技术
【HarmonyOS 5】鸿蒙组件&模板服务详解 - 助力高效开发的利器
在移动应用开发领域,效率与质量始终是开发者追求的核心目标。鸿蒙系统作为新兴的操作系统,为开发者提供了丰富且强大的开发资源,其中鸿蒙组件&模板服务更是成为开发者快速构建高质量应用的得力助手。
136 0
HarmonyOS NEXT仓颉开发语言实战案例:电影App
周末好!本文分享使用仓颉语言重构ArkTS实现的电影App案例,对比两者在UI布局、组件写法及语法差异。内容包括页面结构、列表分组、分类切换与电影展示等。通过代码演示仓颉在HarmonyOS开发中的应用。##仓颉##ArkTS##HarmonyOS开发
|
4月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:健身App
本期分享一个健身App首页的布局实现,顶部采用Stack容器实现重叠背景与偏移效果,列表部分使用List结合Scroll实现可滚动内容。代码结构清晰,适合学习HarmonyOS布局技巧。
HarmonyOS NEXT仓颉开发语言实战案例:小而美的旅行App
本文分享了一个旅行App首页的设计与实现,使用List容器搭配Row、Column布局完成个人信息、功能列表及推荐模块的排版,详细展示了HarmonyOS下的界面构建技巧。
|
4月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:银行App
仓颉语言银行App项目分享,页面布局采用List容器,实现沉浸式体验与模块化设计。顶部资产模块结合Stack与Row布局,背景图与内容分离,代码清晰易懂;功能按钮部分通过负边距实现上移效果,圆角仅保留顶部;热门推荐使用header组件,结构更规范。整体代码风格与ArkTS相似,但细节更灵活,适合金融类应用开发。
|
20天前
|
存储 JSON 数据建模
鸿蒙 HarmonyOS NEXT端云一体化开发-云数据库篇
云数据库采用存储区、对象类型、对象三级结构,支持灵活的数据建模与权限管理,可通过AGC平台或本地项目初始化,实现数据的增删改查及端侧高效调用。
53 0