羽球联盟 HarmonyOS NEXT 实战系列 (11/20):游客与登录用户的数据空间隔离
文章导读
- 游客空间使用
g,登录用户使用u_<uid>,收藏、关注、历史和浏览量都按 scope 隔离。 - 项目没有接后端账号体系,但仍然通过稳定 uid 让同一昵称复用自己的本地空间。
- 登录、退出登录后都会重新灌入当前空间的状态,保证个人中心、详情页和列表页同步刷新。
页面效果
这篇文章对应的用户感知非常明确:游客态和登录态的个人中心统计不能串。游客在首页收藏一篇文章后,如果再用昵称登录,看到的应该是登录用户自己的收藏、关注和浏览记录,而不是把游客数据直接带过来。
从页面上看,这只是一组数字和一个登录入口;从实现上看,它要求持久化、AppStorage 和页面响应式保持同一份事实来源。只要这里处理不好,收藏页、球员关注和浏览历史都会变得不可信。
实战拆解
羽球联盟没有对接服务端账号体系,但它并没有因此忽略“不同用户数据要隔离”这件事。PrefStore先定义一组需要按用户空间隔离的 key:favorites、follows、history、viewCountsJson。随后,所有读写都通过storeKey()拼接 scope 前缀,游客走g,登录用户走u_<uid>。
uid 的生成也很实用。项目没有把昵称直接当成 key 使用,而是通过一个稳定哈希函数把昵称映射成 ASCII uid。这样同一个昵称多次登录会回到同一份本地空间,不同昵称之间又能自然隔离。它当然不能替代真正的账号认证,但对于本地演示、页面联调和教程讲解已经足够。
真正决定页面体验的,是rehydrateScoped()。用户登录、退出登录或重启应用时,当前空间的收藏、关注、历史和浏览量都会重新写回 AppStorage。由于详情页、个人中心、球员页都通过@StorageLink订阅这些状态,所以切换空间后,数字和按钮状态能立即反映当前身份,而不是停留在旧数据上。
关键代码
privatestaticstableUidFor(nickname:string):string{leth:number=5381;for(leti=0;i<nickname.length;i++){h=(((h<<5)+h)+nickname.charCodeAt(i))&0x7fffffff;}return'U_'+h.toString(36).toUpperCase();}privateasyncrehydrateScoped():Promise<void>{constfavStr=awaitthis.store!.get(this.storeKey('favorites'),'[]')asstring;consthistStr=awaitthis.store!.get(this.storeKey('history'),'[]')asstring;constfollowStr=awaitthis.store!.get(this.storeKey('follows'),'[]')asstring;constviewJson=awaitthis.store!.get(this.storeKey('viewCountsJson'),'{}')asstring;AppStorage.setOrCreate<string[]>('favorites',JSON.parse(favStr)asstring[]);AppStorage.setOrCreate<string[]>('history',JSON.parse(histStr)asstring[]);AppStorage.setOrCreate<string[]>('follows',JSON.parse(followStr)asstring[]);AppStorage.setOrCreate<string>('viewCountsJson',viewJson);}asynclogin(nickname:string,avatarUrl?:string):Promise<void>{constuid:string=PrefStore.stableUidFor(nickname.trim());AppStorage.setOrCreate<string>('loggedInUserId',uid);this.scope=`u_\${uid}`;awaitthis.rehydrateScoped();}这段代码的关键点在于“空间切换之后立刻重建当前视图所需状态”。如果只是把昵称和登录态写进 Preferences,而不重新加载当前用户的收藏、关注和历史,界面看上去就像已经登录,但内容仍然是上一位用户的数据。
取舍分析
这个方案最直接的好处,是不需要引入服务端就能把“身份隔离”这件事做对。对于教程型项目来说,这比单纯演示一个登录表单更有价值,因为它把用户态真正落到了收藏、关注和历史这些可见能力上。
它的边界也必须说清楚。基于昵称生成 uid 只是为了本地空间隔离,不是安全认证。如果后续接入真实账号体系,uid 的来源、游客转正式用户的数据迁移、跨设备同步等问题都要重新设计。但在当前项目规模下,这个方案已经足够轻量而且稳定。
设计落点
- 收藏、关注、历史和浏览量都应以当前用户空间为准,而不是放在全局 key 下共享。
- 登录态是全局状态,用户资产是分空间状态,两者要明确区分。
- 空间切换后必须重新写回 AppStorage,页面统计和按钮状态才会同步。
易踩坑
- 不要把收藏、关注、历史全部写成全局 key,否则游客和登录用户会互相污染。
- 不要把
@StorageLink派生结果缓存成普通字段,切换空间后很容易出现界面不同步。 - 本地 uid 只是演示方案,不要把它包装成真实账号安全设计。
验证方式
- 游客先收藏一篇文章,再登录新昵称,确认收藏数切换到该昵称自己的空间。
- 退出登录回到游客态,确认游客收藏和浏览记录仍然存在。
- 用同一昵称再次登录,确认此前该昵称的收藏、关注和历史能被恢复。
参考资料
- Preferences 持久化存储
- ArkUI AppStorage 与状态管理
- ArkUI @StorageLink 装饰器
小结
第 11 篇真正解决的是“同一个设备上的不同身份能不能看见各自的数据”。只要空间前缀、状态回灌和页面响应式三件事配合起来,哪怕没有后端账号体系,这个本地身份闭环也已经足够可靠。