HarmonyOS 登录态治理实战:启动校验、会话续期与多端退出
HarmonyOS 登录态治理实战:启动校验、会话续期与多端退出
登录态问题通常不是“登录接口坏了”,而是多个入口没有统一规则:启动时读到旧 token 就放行,切后台回来没有校验,接口 401 后每个页面各自跳登录,多设备退出后本机仍然显示旧数据。用户看到的是频繁掉线、重复登录或退出不干净,研发排查时却发现日志分散在页面、网络层和缓存层。

本文只解决一个工程问题:在 HarmonyOS 应用中把登录态做成一条可追踪链路,让启动校验、token 续期、接口鉴权、多端退出和本地清理有统一边界。
一、登录态先分四类,不要只看 token 是否存在
很多项目的第一版登录态判断只有一句:本地有 token 就认为已登录。这个判断太粗。token 可能过期、用户可能被后台禁用、其他设备可能已经退出登录。
| 状态 | 含义 | 页面策略 |
|---|---|---|
| anonymous | 本地没有会话 | 进入登录页或游客态 |
| verifying | 本地有会话,正在轻量校验 | 显示启动页或骨架屏 |
| authenticated | 会话有效 | 进入业务首页 |
| expired | 会话过期或被撤销 | 清理本地并跳登录 |

这个状态表要放在应用入口和网络层共同遵守的位置,不能散落在每个页面。
二、资料与版本边界:本文写应用层会话治理
本文示例面向 HarmonyOS NEXT / Stage 模型 / ArkTS 工程,重点在应用层:本地会话模型、启动校验、续期策略、401 收敛、多端退出和清理验收。真实项目还需要结合团队的账号系统、OAuth 或自研 token 方案、加密存储能力、隐私要求和后端接口约定。
| 会话治理层 | 本文处理的职责 | 接入项目时要确认 |
|---|---|---|
| 入口层 | 启动时会话校验和路由选择 | UIAbility 启动链路 |
| 存储层 | accessToken、refreshToken、过期时间 | 加密存储与清理策略 |
| 网络层 | 401 统一处理、续期队列 | 网络库拦截器实现 |
| 多端层 | 远端退出、本机清理 | 账号系统事件通知方式 |
| 验收层 | 弱网、过期、并发、退出测试 | 真机和测试账号 |

三、会话模型:token、用户和过期时间要拆开
会话模型不要只保存一个字符串。至少要区分用户、token、过期时间和刷新窗口。
export interface AccountSession {
userId: string;
accessToken: string;
refreshToken: string;
accessExpireAt: number;
refreshExpireAt: number;
lastVerifyAt: number;
}
export function sessionAccessExpired(session: AccountSession, now: number): boolean {
return now >= session.accessExpireAt;
}
export function sessionCanRefresh(session: AccountSession, now: number): boolean {
return now < session.refreshExpireAt;
}
这段模型的边界是“描述本机会话事实”。它不负责跳转页面,也不直接请求接口。这样做可以避免页面层拿 token 字符串做各种临时判断。
四、启动校验:本地命中后也要轻量确认
启动阶段适合做轻量校验,而不是拉全量用户资料。目标是快速判断能不能进入业务页。
export type StartupAuthRoute = 'LoginPage' | 'HomePage' | 'SplashVerifyingPage';
export interface StartupAuthDecision {
route: StartupAuthRoute;
reason: string;
}
export function decideStartupAuthRoute(
session: AccountSession | undefined,
now: number
): StartupAuthDecision {
if (session === undefined) {
return { route: 'LoginPage', reason: '本地没有会话' };
}
if (sessionAccessExpired(session, now) && !sessionCanRefresh(session, now)) {
return { route: 'LoginPage', reason: '会话已超过可刷新时间' };
}
if (Date.now() - session.lastVerifyAt > 10 * 60 * 1000) {
return { route: 'SplashVerifyingPage', reason: '需要进行启动校验' };
}
return { route: 'HomePage', reason: '本地会话仍在可信窗口内' };
}
这段代码的输入是本地会话和当前时间,输出是启动路由建议。它预防的是“旧 token 直接进首页,接口再一片 401”的体验。
五、续期队列:多个接口同时 401 时只刷新一次
登录态治理最容易写乱的地方是 token 续期。多个接口同时返回 401,如果每个请求都刷新一次,会造成并发风暴。
export class TokenRefreshGate {
private refreshing = false;
private waiters: Array<(success: boolean) => void> = [];
begin(): boolean {
if (this.refreshing) {
return false;
}
this.refreshing = true;
return true;
}
wait(callback: (success: boolean) => void): void {
this.waiters.push(callback);
}
finish(success: boolean): void {
this.refreshing = false;
const callbacks = this.waiters;
this.waiters = [];
for (const callback of callbacks) {
callback(success);
}
}
}
这个 Gate 的职责是“并发收敛”。第一个 401 请求负责刷新,后续请求等待结果。刷新成功后重试原请求,失败后统一清理会话并跳登录。
六、401 处理:网络层只发事件,不直接操作页面
网络层不应该直接弹窗或跳页面。更稳的做法是把鉴权失败封装成事件,由应用状态中心决定如何处理。
export type AuthEventType = 'tokenExpired' | 'refreshFailed' | 'remoteLogout';
export interface AuthEvent {
type: AuthEventType;
traceId: string;
message: string;
timestamp: number;
}
export function createAuthEvent(type: AuthEventType, message: string): AuthEvent {
return {
type,
traceId: `auth_${type}_${Date.now()}`,
message,
timestamp: Date.now()
};
}
事件模型让网络层、页面层和登录模块解耦。接口只负责告诉系统发生了什么,不决定 UI 怎么跳。
七、多端退出:清理范围要比退出按钮更大
多端退出不仅要删 token,还要清理用户资料、草稿、缓存、订阅和正在执行的后台任务。否则下一个账号登录后可能看到前一个账号的数据。
export interface LogoutCleanTask {
name: string;
required: boolean;
done: boolean;
}
export function buildLogoutCleanTasks(): LogoutCleanTask[] {
return [
{ name: '清理会话 token', required: true, done: false },
{ name: '清理用户资料缓存', required: true, done: false },
{ name: '取消账号相关订阅', required: true, done: false },
{ name: '停止待执行上传任务', required: false, done: false },
{ name: '清空敏感草稿', required: true, done: false }
];
}
export function logoutCleanCompleted(tasks: LogoutCleanTask[]): boolean {
return tasks.every(task => !task.required || task.done);
}
这段清理任务表适合放在退出流程里做逐项执行和记录。必需项没完成时,不应该直接进入登录页。
八、登录态问题排查表
| 登录异常表现 | 优先怀疑的会话环节 | 排查入口 | 修复方向 |
|---|---|---|---|
| 启动后首页立刻跳登录 | 启动未做可信窗口判断 | 查看 StartupAuthDecision.reason |
增加校验页或续期流程 |
| 多个接口重复刷新 token | 没有续期并发收敛 | 检查 TokenRefreshGate.begin |
只允许一个刷新请求 |
| 退出后仍看到旧头像 | 用户缓存未清理 | 对照 LogoutCleanTask |
扩大退出清理范围 |
| 401 后页面各跳各的 | 网络层直接控制页面 | 查网络拦截器 | 改为发 AuthEvent |
| 多设备退出不同步 | 缺少远端退出事件 | 查账号事件或轮询校验 | 收到远端退出后清理本机 |
| 日志无法串接口和页面 | 缺少 traceId | 查鉴权事件字段 | 统一 AuthEvent.traceId |
排查顺序是:先看本地会话,再看续期队列,再看网络事件,最后看页面跳转。不要一开始就改登录页 UI。
九、登录态上线前验收表
| 登录态验收点 | 可交付标准 |
|---|---|
| 冷启动校验 | 无 token、有效 token、过期 token 都有明确路径 |
| 并发续期 | 多个接口 401 时只发起一次刷新 |
| 远端退出 | 其他设备退出后本机能感知并清理 |
| 本地清理 | token、用户资料、订阅、敏感草稿都清掉 |
| 弱网恢复 | 续期失败有提示,不会卡在空白页 |
| 日志追踪 | 登录、续期、失败、退出都能用 traceId 串联 |
| 隐私保护 | 日志不输出 accessToken、refreshToken |
验收时至少准备两个账号、两台设备和一个可控过期 token。只测正常登录,覆盖不了会话治理的关键风险。
十、登录态相关官方资料
- 华为开发者文档:Stage 模型应用开发
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/stage-model-development-overview - 华为开发者文档:Want
https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-want - 华为开发者文档:应用文件与数据管理
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-file-access - 华为开发者文档:应用安全与隐私
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/security-privacy-overview
十一、把登录态做成全局基础设施
登录态治理的稳定做法,是把“是否登录”从页面判断升级为全局基础设施。会话模型记录事实,启动决策控制入口,续期队列收敛并发,鉴权事件串起网络层和页面层,退出清理保证数据边界。
| 会话治理问题 | 推荐处理方式 |
|---|---|
| 本地有 token 就能进首页吗 | 不能,还要看过期时间和可信窗口 |
| 多接口 401 怎么办 | 用续期 Gate 收敛为一次刷新 |
| 网络层能跳登录页吗 | 不建议,发鉴权事件给应用状态层 |
| 退出要清理什么 | 会话、缓存、订阅、草稿和任务 |
| 怎么复盘问题 | 用 traceId 串登录、续期、退出链路 |
更多推荐



所有评论(0)