HarmonyOS 6 云开发-用户头像上传云存储

背景

在之前的文章中提到,我们通过Account Kit拿到用户的头像和名称,在软件开发中,我们需要拿到用户信息,需要把用户ID、名字、头像信息存储到云数据库和云存储中。这篇文章主要讲解怎样子获取用户的头像后,并上传用户头像到云存储的过程。

操作流程

  • 用户登录软件,使用AGC的认证服务和华为账号登录。
  • 使用Account Kit 获取用户的头像和名称。
  • 将头像文件从临时链接下载到本地沙箱,再从沙箱中将文件上传到云存储。
  • 获取云存储文件链接,修改AGC账户管理的名称和图片链接。
  • 测试再次打开自动获取图片地址,并下载头像文件到沙箱中,并展示出来。

开发准备

  • 开通AGC云存储服务
  • 认证服务开启华为账号认证(现在手机号码认证需要自己购买第三方服务的形式),这里需要填写Client ID和Client Secret
  • 检查是否已经添加SHA256证书/公钥指纹,没有的时候需要先添加上。
  • 在配置完成后,回到项目设置页面,下载配置文件 "agconnect-services.json" ,放置到项目的AppScope/resources/rawfile目录下,如果没有rawfile文件夹,可以手动创建。需要注意,每次AGC项目设置有变动时,都需要重新下载配置文件。
  • 配置Client ID,在entry模块的"module.json5"文件中,新增metadata,配置name为client_id,value为AGC中获取的Client ID的值。

配置用户认证SDK

  • 在项目的Terminal中,把地址切换到entry文件夹

    cd .\entry\

  • 运行安装SDK命令

    ohpm install @hw-agconnect/auth

  • 在Ability的onCreate方法中初始化,主要代码如下展示
typescript 复制代码
import auth from '@hw-agconnect/auth';
export default class EntryAbility extends UIAbility {
    onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    let file = this.context.resourceManager.getRawFileContentSync('agconnect-services.json');
    let json: string = buffer.from(file.buffer).toString();
    auth.init(this.context, json);
  }
}

使用华为账号登录

登录账号,并完成云服务的初始化。登录方式有很多种,这里选择华为账号登录登录创建用户为了方便演示作用。

typescript 复制代码
      Button("登录")
        .width("80%")
        .onClick(async () => {
          //用户登录
          let user = await auth.getCurrentUser();
          if (!user) {
            try {
              let signInResult: SignInResult = await auth.signIn({
                autoCreateUser: true,
                "credentialInfo": {
                  "kind": 'hwid'
                }

              });
              this.ShowMessage("用户登录成功")
            } catch (e) {
              console.info(e)
            }
          }
          cloudCommon.init({
            region: cloudCommon.CloudRegion.CHINA,
            authProvider: auth.getAuthProvider(),
            functionOptions: { timeout: 10 * 1000 },
            storageOptions: { mode: request.agent.Mode.BACKGROUND, network: request.agent.Network.ANY },
            databaseOptions: { schema: "schema.json", traceId: "traceId" }
          })
        })

获取用户头像和名字

详细的参数解析可以查看 【HarmonyOS6】获取华为用户信息

typescript 复制代码
      Button("获取用户头像和名字")
        .width("80%")
        .onClick(async () => {
          this.AuthRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
          // 获取头像昵称需要传如下scope
          this.AuthRequest.scopes = ['profile', 'openid'];
          // 若开发者需要进行服务端开发,则需传如下permission获取authorizationCode
          this.AuthRequest.permissions = ['serviceauthcode'];
          // 用户是否需要登录授权,该值为true且用户未登录或未授权时,会拉起用户登录或授权页面
          this.AuthRequest.forceAuthorization = false;
          // 用于防跨站点请求伪造
          this.AuthRequest.state = util.generateRandomUUID();
          // 用户没有授权的时候,是否弹窗提示用户授权
          this.AuthRequest.forceAuthorization = true;
          const controller = new authentication.AuthenticationController(this.getUIContext().getHostContext());
          const data: authentication.AuthorizationWithHuaweiIDResponse =
            await controller.executeRequest(this.AuthRequest);
          const authorizationWithHuaweiIDResponse = data as authentication.AuthorizationWithHuaweiIDResponse;
          const state = authorizationWithHuaweiIDResponse.state;
          if (state && this.AuthRequest.state !== state) {
            console.error(`Failed to authorize. The state is different, response state: ${state}`);
            return;
          }
          if (authorizationWithHuaweiIDResponse && authorizationWithHuaweiIDResponse.data) {
            //用户头像链接,有效期较短,建议先将头像下载保存后再使用,这里只是用于演示哈
            if (authorizationWithHuaweiIDResponse.data.avatarUri) {
              this.UserIcon = authorizationWithHuaweiIDResponse.data.avatarUri;
            }
            //用户昵称
            if (authorizationWithHuaweiIDResponse.data.nickName) {
              this.UserName = authorizationWithHuaweiIDResponse.data.nickName;
            }
            //唯一ID
            const userUnionID = authorizationWithHuaweiIDResponse?.data?.unionID;
            //当前应用ID
            const userOpenID = authorizationWithHuaweiIDResponse?.data?.openID;
          }
        })

将头像上传到云存储并获取图片下载地址

将头像上传到云存储的固定文件夹目录中,然后通过StorageBucket.getDownloadURL方法,获取图片的下载路径,下面仅展示关键代码

typescript 复制代码
      Button("根据用户头像文件上传云存储")
        .width("80%")
        .onClick(async () => {
          let photoName: string = `AccountDemo_${this.UserName}.png`;
          //拼接沙箱存储地址
          let cachePath: string = `${GlobalContext.getContext().cacheDir}/${photoName}`
          //检查沙箱中图片是否存在
          if (!(await fileIo.access(cachePath))) {
            await this.DownloadIconToCache(cachePath, () => {
              this.SendIconToCloud(cachePath, photoName, () => {
                this.UpdateUserIconAndName(photoName)
              });
            });
          } else {
            //上传图片
            this.SendIconToCloud(cachePath, photoName, () => {
              this.UpdateUserIconAndName(photoName)
            });
          }
        })

完整代码

Index.ets

typescript 复制代码
import { ImageType } from '@kit.UIDesignKit'
import { authentication } from '@kit.AccountKit'
import { util } from '@kit.ArkTS'
import { GlobalContext } from '../Common/GlobalContext'
import { request, BusinessError } from '@kit.BasicServicesKit'
import { cloudCommon, cloudStorage } from '@kit.CloudFoundationKit'
import fileIo from '@ohos.file.fs'
import auth, { AuthUser, SignInResult } from '@hw-agconnect/auth'

@Entry
@ComponentV2
struct Index {
  @Local UserIcon: ImageType = $r('app.media.user_dark')
  @Local UserName: string = "炸鸡仔"
  AuthRequest?: authentication.AuthorizationWithHuaweiIDRequest
  UIContext: UIContext = this.getUIContext()
  /**
   * 从云存储下载的头像文件
   */
  @Local CloudIcon: ImageType = $r('app.media.user_dark')
  /**
   * 云存储实例
   */
  StorageBucket: cloudStorage.StorageBucket = cloudStorage.bucket();

  build() {
    Column({ space: 10 }) {
      Button("登录")
        .width("80%")
        .onClick(async () => {
          //用户登录
          let user = await auth.getCurrentUser();
          if (!user) {
            try {
              let signInResult: SignInResult = await auth.signIn({
                autoCreateUser: true,
                "credentialInfo": {
                  "kind": 'hwid'
                }

              });
              this.ShowMessage("用户登录成功")
            } catch (e) {
              console.info(e)
            }

          } else {
            this.ShowMessage("用户已经登录")
          }
          cloudCommon.init({
            region: cloudCommon.CloudRegion.CHINA,
            authProvider: auth.getAuthProvider(),
            functionOptions: { timeout: 10 * 1000 },
            storageOptions: { mode: request.agent.Mode.BACKGROUND, network: request.agent.Network.ANY },
            databaseOptions: { schema: "schema.json", traceId: "traceId" }
          })
        })

      Image(this.UserIcon)
        .width(80)
        .height(80)
        .borderRadius(40)
      Text(this.UserName)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 5 })
      Button("获取用户头像和名字")
        .width("80%")
        .onClick(async () => {
          this.AuthRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
          // 获取头像昵称需要传如下scope
          this.AuthRequest.scopes = ['profile', 'openid'];
          // 若开发者需要进行服务端开发,则需传如下permission获取authorizationCode
          this.AuthRequest.permissions = ['serviceauthcode'];
          // 用户是否需要登录授权,该值为true且用户未登录或未授权时,会拉起用户登录或授权页面
          this.AuthRequest.forceAuthorization = false;
          // 用于防跨站点请求伪造
          this.AuthRequest.state = util.generateRandomUUID();
          // 用户没有授权的时候,是否弹窗提示用户授权
          this.AuthRequest.forceAuthorization = true;
          const controller = new authentication.AuthenticationController(this.getUIContext().getHostContext());
          const data: authentication.AuthorizationWithHuaweiIDResponse =
            await controller.executeRequest(this.AuthRequest);
          const authorizationWithHuaweiIDResponse = data as authentication.AuthorizationWithHuaweiIDResponse;
          const state = authorizationWithHuaweiIDResponse.state;
          if (state && this.AuthRequest.state !== state) {
            console.error(`Failed to authorize. The state is different, response state: ${state}`);
            return;
          }
          if (authorizationWithHuaweiIDResponse && authorizationWithHuaweiIDResponse.data) {
            //用户头像链接,有效期较短,建议先将头像下载保存后再使用,这里只是用于演示哈
            if (authorizationWithHuaweiIDResponse.data.avatarUri) {
              this.UserIcon = authorizationWithHuaweiIDResponse.data.avatarUri;
            }
            //用户昵称
            if (authorizationWithHuaweiIDResponse.data.nickName) {
              this.UserName = authorizationWithHuaweiIDResponse.data.nickName;
            }
            //唯一ID
            const userUnionID = authorizationWithHuaweiIDResponse?.data?.unionID;
            //当前应用ID
            const userOpenID = authorizationWithHuaweiIDResponse?.data?.openID;
          }
        })
      Button("根据用户头像文件上传云存储")
        .width("80%")
        .onClick(async () => {
          let photoName: string = `AccountDemo_${this.UserName}.png`;
          //拼接沙箱存储地址
          let cachePath: string = `${GlobalContext.getContext().cacheDir}/${photoName}`
          //检查沙箱中图片是否存在
          if (!(await fileIo.access(cachePath))) {
            await this.DownloadIconToCache(cachePath, () => {
              this.SendIconToCloud(cachePath, photoName, () => {
                this.UpdateUserIconAndName(photoName)
              });
            });
          } else {
            //上传图片
            this.SendIconToCloud(cachePath, photoName, () => {
              this.UpdateUserIconAndName(photoName)
            });
          }
        })
      Image(this.CloudIcon)
        .width(80)
        .height(80)
        .borderRadius(40)
      Button("获取用户头像")
        .width("80%")
        .onClick(async () => {
          let user = await auth.getCurrentUser();
          if (user) {
            this.CloudIcon = user.getPhotoUrl();
          }
        })
    }
    .height('100%')
    .width('100%')
  }

  private ShowMessage(value: string) {
    console.info(value)
    this.UIContext.getPromptAction().showToast({
      message: value,
      alignment: Alignment.Center,
      duration: 1000
    })
  }

  /**
   * 上传图片到云存储
   * @param cachePath
   * @param photoName
   */
  private async SendIconToCloud(cachePath: string, photoName: string, completedEvent: () => void) {
    try {
      //上传云存储
      this.StorageBucket.uploadFile(GlobalContext.getContext(), {
        localPath: cachePath,
        cloudPath: `AccountDemo/${photoName}`
      }).then((cloudTask: request.agent.Task) => {
        //订阅任务进度的事件
        cloudTask.on('progress', (progress) => {
        });
        //订阅任务完成事件
        cloudTask.on('completed', (progress) => {
          this.ShowMessage("完成图片上传");
          completedEvent();
        });
        //订阅任务失败事件
        cloudTask.on('failed', (progress: request.agent.Progress) => {
          this.ShowMessage("上传错误");
        });
        cloudTask.start((err: BusinessError) => {
          if (err) {
            this.ShowMessage("上传错误" + err.message);
          }
        })
      })

    } catch (e) {
      console.error(e)
    }
  }

  /**
   * 下载图片到沙箱
   * @param cachePath
   * @param completedEvent
   */
  private async DownloadIconToCache(cachePath: string, completedEvent: () => void) {
    //下载图片保存到沙箱地址
    const task = await request.downloadFile(GlobalContext.getContext(), {
      url: this.UserIcon as string,
      filePath: cachePath
    })
    task.on('progress', (value) => {
      //可以做进度条展示
      console.info(`正在下载图片${value}%`)
    })
    task.on("complete", async () => {
      //真的下载完成了
      this.ShowMessage("网络图片下载完成了");
      completedEvent();
    })
    task.on("fail", () => {
      this.ShowMessage("图片下载失败")
    })
  }

  /**
   * 更改用户名字和头像
   * @param photoName
   */
  private async UpdateUserIconAndName(photoName: string) {
    //获取图片下载地址
    let iconUrl: string = await this.StorageBucket.getDownloadURL(`AccountDemo/${photoName}`)
    //修改用户名字
    let user = await auth.getCurrentUser();
    if (user) {
      try {
        await user.updateProfile({
          displayName: this.UserName,
          photoUrl: iconUrl
        })
        this.ShowMessage("完成用户名字修改")
      } catch (e) {
        console.info(e);
      }
    }
  }
}

GlobalContext

typescript 复制代码
import { common } from '@kit.AbilityKit';

export class GlobalContext {
  private static context: common.UIAbilityContext;

  public static initContext(context: common.UIAbilityContext): void {
    GlobalContext.context = context;
  }

  public static getContext(): common.UIAbilityContext {
    return GlobalContext.context;
  }
}

EntryAbility

typescript 复制代码
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { GlobalContext } from '../Common/GlobalContext';
import { buffer } from '@kit.ArkTS';
import auth from '@hw-agconnect/auth';

const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
    GlobalContext.initContext(this.context);
    let file = this.context.resourceManager.getRawFileContentSync('agconnect-services.json');
    let json: string = buffer.from(file.buffer).toString();
    auth.init(this.context, json);
  }

  onDestroy(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }

  onWindowStageDestroy(): void {
    // Main window is destroyed, release UI related resources
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
    // Ability has brought to foreground
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
    // Ability has back to background
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

结果演示

用户信息和云存储均已开通

操作视频

总结

tageDestroy');

}

onForeground(): void {

// Ability has brought to foreground

hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');

}

onBackground(): void {

// Ability has back to background

hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');

}

}

复制代码
# 结果演示

## 用户信息和云存储均已开通

[外链图片转存中...(img-Qdb3dZDF-1753694903856)]

[外链图片转存中...(img-sptYJ2Mz-1753694903856)]

## 操作视频

[外链图片转存中...(img-9KHUdknI-1753694903856)]

# 总结

希望这篇文章可以让你更了解云存储的使用,上传图片的操作方式。
相关推荐
鸿蒙先行者11 分钟前
鸿蒙开发ArkUI框架布局与适配难题丛生之响应式布局实现艰难
harmonyos·arkui
ajassi20001 小时前
开源 Arkts 鸿蒙应用 开发(十七)通讯--http多文件下载
华为·开源·harmonyos
前端世界1 小时前
在鸿蒙里优雅地处理网络错误:从 Demo 到实战案例
网络·华为·harmonyos
changsanjiang1 小时前
鸿蒙 音视频边播放边缓存
harmonyos
极限实验室2 小时前
喜报!极限科技获得国际专利正式授权——美国发明专利《Data Partitioning Method and Data Processing Method》
数据挖掘
用户199701080182 小时前
抖音商品列表API技术文档
大数据·数据挖掘·数据分析
木木子99992 小时前
第5问 对于数据分析领域,统计学要学到什么程度?
数据挖掘·数据分析
Georgewu5 小时前
【HarmonyOS】应用调用相机功能(扫码,自定义相机,人脸活体检测等)显示黑屏
harmonyos
胡耀超6 小时前
从哲学(业务)视角看待数据挖掘:从认知到实践的螺旋上升
人工智能·python·数据挖掘·大模型·特征工程·crisp-dm螺旋认知·批判性思维
文博知浅6 小时前
时隔4个月,500+star,鸿蒙ArkTS vscode插件1.x已发布🎉完全重构,补全、类型提示、SDK下载管理切换一应俱全,更多新功能正在规划中...
前端·javascript·harmonyos