前言
AccountKit是 HarmonyOS 的帐号服务能力,支持用户通过华为帐号登录应用。对于「猫猫大作战」这类带有排行榜和好友系统的游戏,接入华为帐号登录可以实现跨设备数据同步、好友排名等功能。用户无需注册新账号,一键授权即可完成登录,大幅降低使用门槛。
本文以「猫猫大作战」的华为帐号集成为锚点,讲解 AccountKit 的完整登录流程,包括帐号获取、令牌验证、登录状态管理、以及退出登录。
提示:本系列不讲 ArkTS 基础语法与环境搭建。本篇是阶段六第 156 篇。
一、AccountKit 基础概念
1.1 登录流程
用户点击"华为登录"按钮 │ ▼ huaweiAccount.getAccount() ───► 获取华为帐号对象 │ ▼ account.getToken() ───► 获取登录令牌 (Token) │ ▼ 服务端验证 Token ───► 返回用户身份标识 (UnionID) │ ▼ 登录成功,进入游戏1.2 模块导入
import { huaweiAccount } from '@kit.AccountKit'; import { BusinessError } from '@kit.BasicServicesKit';提示:
@kit.AccountKit是 HarmonyOS 统一 Kit 导入方式,替代了旧的@ohos.account.huaweiAccount。
二、华为帐号登录
2.1 获取帐号和 Token
async function loginWithHuaweiID(): Promise<LoginResult> { try { // 步骤 1:获取华为帐号 const account = await huaweiAccount.getAccount(); console.info(`获取到华为帐号: ${account.getDisplayName()}`); // 步骤 2:获取登录令牌 const token = await account.getToken(); console.info(`获取到 Token: ${token.substring(0, 10)}...`); // 步骤 3:将 Token 发送到服务端验证 const userInfo = await verifyTokenOnServer(token); return { success: true, unionId: userInfo.unionId, displayName: userInfo.displayName, avatar: userInfo.avatar, }; } catch (err) { const bizErr = err as BusinessError; console.error(`登录失败: code=${bizErr.code}, message=${bizErr.message}`); if (bizErr.code === 401) { // 用户取消授权 return { success: false, error: '用户取消登录' }; } return { success: false, error: `登录失败: ${bizErr.message}` }; } } interface LoginResult { success: boolean; unionId?: string; displayName?: string; avatar?: string; error?: string; }2.2 Token 验证(服务端)
// 服务端验证 Token 的示例(Node.js) // 客户端将 Token 发送给服务端,服务端调用华为接口验证 async function verifyTokenOnServer(token: string): Promise<UserInfo> { const response = await fetch('https://api.example.com/auth/huawei', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ token }), }); if (!response.ok) { throw new Error(`Token 验证失败: ${response.status}`); } return await response.json(); } interface UserInfo { unionId: string; // 用户唯一标识(跨应用不变) displayName: string; // 用户昵称 avatar: string; // 头像 URL }三、登录状态管理
3.1 持久化登录状态
import { preferences } from '@kit.ArkData'; class AuthManager { private static instance: AuthManager; private userInfo: UserInfo | null = null; private preferencesStore: preferences.Preferences | null = null; static getInstance(): AuthManager { if (!AuthManager.instance) { AuthManager.instance = new AuthManager(); } return AuthManager.instance; } async init(context: common.UIAbilityContext): Promise<void> { this.preferencesStore = await preferences.getPreferences(context, 'auth_prefs'); // 尝试恢复登录状态 const savedUnionId = this.preferencesStore.get('unionId', '') as string; if (savedUnionId) { this.userInfo = { unionId: savedUnionId, displayName: this.preferencesStore.get('displayName', '') as string, avatar: this.preferencesStore.get('avatar', '') as string, }; } } async login(): Promise<boolean> { const result = await loginWithHuaweiID(); if (result.success && result.unionId) { this.userInfo = { unionId: result.unionId, displayName: result.displayName ?? '玩家', avatar: result.avatar ?? '', }; // 持久化 await this.preferencesStore?.put('unionId', this.userInfo.unionId); await this.preferencesStore?.put('displayName', this.userInfo.displayName); await this.preferencesStore?.put('avatar', this.userInfo.avatar); await this.preferencesStore?.flush(); return true; } return false; } async logout(): Promise<void> { this.userInfo = null; await this.preferencesStore?.delete('unionId'); await this.preferencesStore?.delete('displayName'); await this.preferencesStore?.delete('avatar'); await this.preferencesStore?.flush(); } get isLoggedIn(): boolean { return this.userInfo !== null; } get displayName(): string { return this.userInfo?.displayName ?? '游客'; } }3.2 登录状态的生命周期
@Entry @Component struct GameApp { @State isLoggedIn: boolean = false; @State playerName: string = '游客'; private authManager = AuthManager.getInstance(); aboutToAppear() { const ctx = getContext() as common.UIAbilityContext; this.authManager.init(ctx).then(() => { this.isLoggedIn = this.authManager.isLoggedIn; this.playerName = this.authManager.displayName; }); } build() { Column() { Text(`欢迎, ${this.playerName}`) .fontSize(18) .margin({ bottom: 20 }) if (!this.isLoggedIn) { Button('华为帐号登录') .onClick(async () => { const success = await this.authManager.login(); if (success) { this.isLoggedIn = true; this.playerName = this.authManager.displayName; promptAction.showToast({ message: '登录成功!' }); } }) } else { Button('退出登录') .onClick(async () => { await this.authManager.logout(); this.isLoggedIn = false; this.playerName = '游客'; }) } } .width('100%') .padding(24) } }四、登录按钮 UI
4.1 样式规范
@Builder HuaweiLoginButton(onLogin: () => void) { Button({ icon: $r('app.media.ic_huawei_logo'), text: '华为帐号登录' }) .width('280vp') .height('48vp') .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#FFFFFF') .backgroundColor('#C8102E') // 华为品牌红 .borderRadius(24) .onClick(() => onLogin()) }4.2 错误状态处理
// 登录失败的不同场景 enum LoginErrorType { USER_CANCEL, // 用户取消 NETWORK_ERROR, // 网络错误 TOKEN_EXPIRED, // Token 过期 SERVER_ERROR, // 服务端验证失败 UNKNOWN, // 未知错误 } function handleLoginError(err: BusinessError): LoginErrorType { switch (err.code) { case 401: return LoginErrorType.USER_CANCEL; case 402: return LoginErrorType.TOKEN_EXPIRED; case 500: return LoginErrorType.SERVER_ERROR; default: if (err.message.includes('network')) { return LoginErrorType.NETWORK_ERROR; } return LoginErrorType.UNKNOWN; } }五、Token 的缓存与刷新
5.1 Token 缓存策略
class TokenManager { private token: string | null = null; private expiryTime: number = 0; async getValidToken(): Promise<string | null> { // 如果 Token 还有效,直接返回 if (this.token && Date.now() < this.expiryTime) { return this.token; } // Token 过期或不存在,重新获取 try { const account = await huaweiAccount.getAccount(); this.token = await account.getToken(); this.expiryTime = Date.now() + 3600 * 1000; // 假设 1 小时有效 return this.token; } catch { return null; } } clearToken(): void { this.token = null; this.expiryTime = 0; } }5.2 Token 刷新时机
| 时机 | 操作 | 说明 |
|---|---|---|
| 应用启动 | 检查 Token | 从缓存加载 |
| Token 过期 | 静默刷新 | account.getToken()重新获取 |
| 登录状态校验失败 | 重新登录 | 引导用户重新授权 |
| 退出登录 | 清除 Token | clearToken() |
六、AccountKit API 详解
6.1 获取帐号信息
const account = await huaweiAccount.getAccount(); // 可用方法 account.getDisplayName(); // 用户昵称 account.getAvatarUri(); // 头像 URI account.getUnionId(); // 应用内唯一 ID(同一应用内不变) account.getOpenId(); // 开发者账号维度的用户 ID account.getToken(); // 登录令牌(服务端验证用)6.2 方法对比
| 方法 | 返回值 | 唯一性 | 用途 |
|---|---|---|---|
getUnionId() | string | 同一应用内唯一 | 用户身份标识 |
getOpenId() | string | 同一开发者帐号内唯一 | 跨应用识别 |
getToken() | string | 临时令牌 | 服务端验证 |
getDisplayName() | string | 可变 | UI 展示 |
提示:
UnionID是推荐的用户标识字段——同一应用内不同设备间唯一且不变,适合作为数据库中的用户主键。
七、游戏中的数据绑定
// 登录后绑定游戏数据 @Entry @Component struct GameMain { @State playerName: string = '游客'; @State highScore: number = 0; @State rank: number = -1; async onLoginSuccess(unionId: string, name: string) { this.playerName = name; // 从服务端拉取用户数据 const userData = await fetchUserData(unionId); if (userData) { this.highScore = userData.highScore; this.rank = userData.rank; // 更新本地最高分 AppStorage.setOrCreate('highScore', userData.highScore); } } }八、常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
getAccount()抛出 401 | 用户取消 | 展示登录按钮让用户重试 |
| Token 为空 | 网络异常 | 检查网络后重试 |
| 服务端验证失败 | Token 过期 | 重新获取 Token |
| 登录后界面未更新 | 状态未同步 | 检查 @State 是否更新 |
| 退出登录后仍有数据 | 缓存未清 | 调用clearToken() |
九、安全注意事项
// ⚠️ 安全性提示 // 1. Token 不能泄露到客户端日志中 console.info(`Token: ${token}`); // 🚫 禁止! // 2. Token 验证必须在服务端完成 // 🚫 错误:在客户端直接验证 const isValid = token.startsWith('valid'); // ❌ // 3. 使用 HTTPS 传输 Token // ✅ 正确:通过 HTTPS POST 发送到服务端 fetch('https://api.example.com/auth/verify', { ... }) // 4. 本地不要存储原始 Token // ✅ 正确:只存 UnionID 用于识别 preferences.put('unionId', account.getUnionId());十、AccountKit vs 其他登录方式
| 登录方式 | 集成复杂 | 用户门槛 | 数据同步 | 适用场景 |
|---|---|---|---|---|
| 华为帐号 | 低 | 低(已有帐号) | ✅ | 游戏主登录 |
| 手机号 | 高(短信) | 中 | ❌ | 无华为设备 |
| 匿名 | 无 | 无 | ❌ | 快速体验 |
| 第三方登录 | 高(对接) | 中 | ✅ | 多平台 |
总结
AccountKit 通过huaweiAccount.getAccount()+account.getToken()实现一键登录,结合服务端 Token 验证和本地持久化构建完整的登录体系。核心要点:getAccount()获取华为帐号、getToken()获取登录令牌、 服务端验证 Token 并返回 UnionID、 本地持久化 UnionID 实现自动登录、 Token 缓存与静默刷新、 退出时清除所有认证数据。
下一篇将深入 LoginWithHuaweiIDButton——华为一键登录按钮组件的封装。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- AccountKit 官方文档
- huaweiAccount API 参考
- AGC 服务端验证
- UnionID 说明
- Preferences 持久化
- 华为登录设计规范
- 开源鸿蒙跨平台社区
- HarmonyOS 开发者官方文档
- 第 155 篇:SharedData 数据分享
- 第 157 篇:LoginButton 一键登录