第20篇:华为一键登录与数据备份

在移动应用中,用户身份认证和数据备份是提升用户体验和数据安全的关键能力。HarmonyOS 提供了华为账号一键登录和端云数据备份机制,"柚兔学伴"项目通过 backup_air 第三方库将两者整合,实现了无缝的登录流程和自动化的数据备份恢复。本篇详细讲解其实现方式。


在这里插入图片描述

1. backup_air 库概述

backup_air 是项目使用的第三方库,封装了华为账号登录、云端备份恢复和用户信息管理能力。主要提供以下核心类:

类名 职责
BackupManager 备份恢复管理,含登录、备份、恢复操作
BackupDir 备份目录配置
BackupConfig 备份配置读取
UserInfoManager 用户信息管理(登录状态、Token、用户资料)

2. BackupManager 初始化

2.1 BackupManagerService 单例

项目通过 BackupManagerService 单例封装 BackupManager 的初始化和状态管理:

import { BackupDir, BackupManager } from 'backup_air';
import { DbUtil } from '../util/Dbutil';

export class BackupManagerService {
  private static instance: BackupManagerService;
  private isInitialized: boolean = false;
  private isLoggedIn: boolean = false;
  private userInfo: string = '未登录';

  private constructor() {}

  public static getInstance(): BackupManagerService {
    if (!BackupManagerService.instance) {
      BackupManagerService.instance = new BackupManagerService();
    }
    return BackupManagerService.instance;
  }

  public isBackupManagerInitialized(): boolean {
    return this.isInitialized;
  }

  public getLoginState(): boolean {
    return this.isLoggedIn;
  }

  public getUserInfo(): string {
    return this.userInfo;
  }

  private updateLoginState() {
    this.isLoggedIn = true;
    this.userInfo = '已登录';
  }
}

2.2 初始化配置

initializeBackupManager() 是核心初始化方法,配置了备份恢复的所有参数:

public async initializeBackupManager(): Promise<void> {
  try {
    const backupManager = BackupManager.getInstance();
    let store = (await DbUtil.getInstance(getContext().getApplicationContext())).store;

    if (!store) {
      throw new Error('未初始化数据库');
    }

    backupManager.init({
      onCompleteBackup: () => {
        console.log('自定义回调- 备份完成');
      },
      onCompleteRestore: () => {
        console.log('自定义回调- 恢复完成');
      },
      updateDbVersion: (cloudDbVersion) => {
        console.log("自定义回调- 更新数据库版本 云端数据库版本" + cloudDbVersion);
      },
      cloudDir: `testUser`,
      cloudDbName: 'Partner.db',
      storeConfig: DbUtil.STORE_CONFIG,
      backupDirs: [new BackupDir("testDir", "testCloud.zip")],
      cloudStorageType: 'sdk',
      bucketName: "partner-xhmds",
      productId: '461323198430304163',
      client_id: '1766045115320685696',
      client_secret: '34E13149AF818215E17E6192F0059E95B7517DB143C524D29EC8044699497C5E',
      oauth_client_id: "6917583027092394696",
      oauth_client_secret: "91089c76b37141672901fe606ad38de79bf51138544988765d3cbd4ccd1ff435",
    }, store);

    this.isInitialized = true;
    this.updateLoginState();
    console.log('BackupManager初始化成功');
  } catch (error) {
    console.error('BackupManager初始化失败:', error);
    promptAction.showToast({ message: 'BackupManager初始化失败' });
    this.isInitialized = false;
    this.isLoggedIn = false;
    this.userInfo = '初始化失败,未登录';
  }
}

2.3 配置参数详解

参数 说明
onCompleteBackup 备份完成回调,可在其中刷新 UI
onCompleteRestore 恢复完成回调,通常需要重新加载数据
updateDbVersion 云端数据库版本变更回调,用于处理数据迁移
cloudDir 用户在云存储中的目录,不同用户应使用不同目录
cloudDbName 云端存储的数据库文件名,这里是 Partner.db
storeConfig 本地 RDB 数据库配置,备份恢复的数据载体
backupDirs 需要备份的文件目录列表,BackupDir(本地目录, 云端压缩包名)
cloudStorageType 'sdk' 需要登录认证,'http' 不需要登录
bucketName 云存储桶名称
productId AGC 项目 ID
client_id / client_secret ServerSDK API 凭证
oauth_client_id / oauth_client_secret OAuth2 凭证,用于 Token 刷新

cloudStorageType 选择:

  • 'sdk':使用云存储 SDK 模式,需要用户登录华为账号,安全性更高
  • 'http':使用 HTTP 模式,不需要登录,适合公开数据的备份

3. 华为一键登录

3.1 登录流程

MineView 中,用户点击头像触发登录:

login() {
  const backupManager = BackupManager.getInstance();
  if (!BackupManagerService.getInstance().isBackupManagerInitialized()) {
    promptAction.showToast({ message: '等待初始化完成' });
    return;
  }
  this.isLoggedIn = UserInfoManager.isLoggedIn();
  if (this.isLoggedIn) {
    return
  }
  try {
    backupManager.login();
    setTimeout(() => {
      this.updateLoginState();
      if (this.isLoggedIn) {
        // 登录成功处理
      } else {
        // 登录失败处理
      }
    }, 1000);
  } catch (error) {
    console.error('登录失败:', error);
  }
}

登录流程时序:

用户点击头像
  → 检查 BackupManager 是否初始化
  → 检查是否已登录
  → backupManager.login() 弹出华为账号授权页
  → 用户授权后华为返回 Token
  → UserInfoManager 自动存储用户信息
  → setTimeout 延迟后读取登录状态
  → 更新 UI

3.2 登录状态更新

private updateLoginState() {
  this.isLoggedIn = UserInfoManager.isLoggedIn();
  if (this.isLoggedIn) {
    const userInfoData = UserInfoManager.getUserInfo();
    this.userInfo = `${userInfoData?.nickName || '未登录'}`;
    this.avatar = userInfoData?.avatarUri || $r('app.media.ic_avatar')
  } else {
    this.userInfo = '未登录';
    this.avatar = $r('app.media.ic_avatar')
  }
}

3.3 UserInfoManager API

UserInfoManager 提供了完整的用户信息管理接口:

方法 返回值 说明
isLoggedIn() boolean 是否已登录
getUserInfo() UserInfo | undefined 获取用户信息
getAccessToken() string 获取访问令牌
getRefreshToken() string 获取刷新令牌
isAccessTokenExpired() boolean AccessToken 是否过期
isRefreshTokenExpired() boolean RefreshToken 是否过期
updateTokens(token, expire) void 更新 Token
clearUserInfo() void 清除用户信息(登出)

用户信息对象包含以下字段:

UserInfo {
  openID: string       // 用户唯一标识
  nickName: string     // 昵称
  avatarUri: string    // 头像 URI
  unionID: string      // 跨应用唯一标识
}

4. 登录拦截:CustomTabBar

应用首页底部导航栏实现了登录拦截——非首页的 Tab 切换需要用户先登录:

@Component
export struct CustomTabBar {
  @State isLoggedIn: boolean = false
  @Prop @Require currentIndex: number;
  tabBarChange: (index: number) => void = (index: number) => {};

  @Builder
  TabItemBuilder(tabBar: TabBarData) {
    Column() {
      SymbolGlyph(tabBar.icon)
        .fontSize($r('sys.float.Title_M'))
        .fontColor(tabBar.id === this.currentIndex ? [$r('app.color.app_primary')] :
          [$r('sys.color.font_tertiary')])
      Text(tabBar.title)
        .fontSize($r('sys.float.Caption_M'))
        .margin({ top: $r('sys.float.padding_level1') })
        .fontWeight(FontWeight.Medium)
    }
    .onClick(() => {
      this.isLoggedIn = UserInfoManager.isLoggedIn();
      if (!this.isLoggedIn && tabBar.id !== 0) {
        this.login()
      } else {
        if (this.currentIndex !== tabBar.id) {
          this.tabBarChange(tabBar.id);
        }
      }
    })
  }
}

拦截逻辑:

  1. 每次点击 Tab 时检查 UserInfoManager.isLoggedIn()
  2. 未登录且点击的不是首页(tabBar.id !== 0),触发登录流程
  3. 已登录或点击首页,正常切换 Tab

4.1 TabBar 中的登录调用

login() {
  const backupManager = BackupManager.getInstance();
  if (!BackupManagerService.getInstance().isBackupManagerInitialized()) {
    promptAction.showToast({ message: '等待初始化完成' });
    return;
  }
  this.isLoggedIn = UserInfoManager.isLoggedIn();
  if (this.isLoggedIn) {
    return
  }
  try {
    backupManager.login();
    setTimeout(() => {
      if (this.isLoggedIn) {
        // 登录成功
      } else {
        // 登录失败
      }
    }, 1000);
  } catch (error) {
    console.error('登录失败:', error);
  }
}

5. 用户界面展示

5.1 MineView 个人中心

个人中心页面展示用户头像、昵称和功能菜单:

@Component
export struct MineView {
  @State isInitialized: boolean = false;
  @State isLoggedIn: boolean = false;
  @State userInfo: string = '未登录';
  @State userNickname: string = '柚兔';
  @State avatar: Resource | string = $r('app.media.ic_avatar');

  aboutToAppear(): void {
    this.updateLoginState()
  }

  build() {
    Column() {
      Row({ space: 10 }) {
        Image(this.avatar).width(48)
          .clip(true)
          .borderRadius(30)
          .onClick(() => {
            if (!this.isLoggedIn) {
              this.login()
            }
          })
        Column({ space: 8 }) {
          Text(this.userInfo).fontSize(16).fontWeight(FontWeight.Bold)
          Text('书山有路勤为径').fontColor($r('app.color.color_sub_text')).fontSize(12)
        }.layoutWeight(1).alignItems(HorizontalAlign.Start)

        Image($r('app.media.ic_setting')).width(32)
          .onClick(() => {
            this.pageContext.openPage({
              routerName: 'SettingPage',
              onReturn: async (data) => {
                this.updateLoginState()
              }
            }, true);
          })
      }
      .width('100%')
      .padding(12)
      .borderRadius(5)
      .backgroundColor($r('app.color.color_card'))
    }
  }
}

UI 状态映射:

登录状态 头像 昵称文本
未登录 默认头像 ic_avatar “未登录”
已登录 用户华为头像 avatarUri 用户昵称

5.2 未登录头像点击

头像区域是一个隐式的登录入口——未登录时点击头像触发 login() 方法,已登录时不响应。这种设计比专门的登录按钮更加自然。

6. 登出流程

通过 UserInfoManager.clearUserInfo() 清除用户信息实现登出:

// 在需要登出的地方调用
UserInfoManager.clearUserInfo()

登出后所有依赖登录状态的功能都会自动降级:

  • 云存储:MyAuthProvider 检测到未登录返回空 Token
  • 云数据库:查询中 unionID 为空,无法获取用户数据
  • TabBar 拦截:非首页 Tab 被拦截,引导用户重新登录

7. 备份恢复机制

7.1 备份触发

BackupManager 初始化后,备份和恢复通常在以下时机触发:

  • 备份:用户数据变更后、应用切换到后台、用户手动触发
  • 恢复:应用首次安装、用户在新设备登录

7.2 备份内容

根据配置,备份涉及两类数据:

  1. 数据库文件cloudDbName: 'Partner.db',将本地 RDB 数据库备份到云端
  2. 文件目录backupDirs: [new BackupDir("testDir", "testCloud.zip")],将指定目录打包为 zip 上传

7.3 版本管理

updateDbVersion 回调在云端数据库版本与本地不一致时触发,用于处理数据迁移:

updateDbVersion: (cloudDbVersion) => {
  console.log("云端数据库版本:" + cloudDbVersion);
  // 执行数据迁移逻辑
}

8. 安全性考量

8.1 凭证管理

项目中凭证直接写在代码中,实际生产环境建议:

  • client_idclient_secret 等凭证移至服务端
  • 客户端通过自有服务获取临时凭证
  • 使用环境变量或配置文件区分开发/生产环境

8.2 Token 刷新

Token 刷新机制确保长期登录态:

AccessToken 过期
  → MyAuthProvider 检测过期
  → refreshAuthToken() 使用 RefreshToken 刷新
  → RefreshToken 也过期
  → 提示用户重新登录
  → clearUserInfo() 清除本地状态

小结

本篇围绕华为一键登录和数据备份展开讲解:

  1. 初始化配置BackupManager.init() 配置回调、云端目录、存储类型和 API 凭证
  2. 一键登录backupManager.login() 弹出华为账号授权页,授权后 UserInfoManager 自动管理状态
  3. 用户信息UserInfoManager.isLoggedIn() 检查状态,getUserInfo() 获取昵称、头像和 unionID
  4. 登录拦截:CustomTabBar 在非首页 Tab 切换时检查登录态,未登录则触发授权
  5. 状态展示:MineView 根据登录状态动态切换头像和昵称
  6. 数据备份:配置 cloudDbNamebackupDirs 指定备份内容,回调处理完成和版本更新
  7. 安全机制:Token 自动刷新,过期引导重新登录

华为一键登录和数据备份为"柚兔学伴"提供了零门槛的身份认证和跨设备数据同步能力,让用户的学习进度和偏好设置在多设备间无缝流转。

Logo

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

更多推荐