HarmonyOS 6.0 路由守卫与登录拦截:HarmonyOS里的“门卫“怎么当
路由守卫与登录拦截:HarmonyOS 里的"门卫"怎么当
做移动端开发,路由守卫这事绕不开。Web 前端有 vue-router 的 beforeEach,Android 有 Activity 的 onNewIntent 拦截,到了 HarmonyOS呢?Navigation + NavPathStack 这套体系给你留了几个口子,但不像 Web 端那么现成,得自己拼。今天就把这套"门卫体系"从零到一捋清楚。
一、路由前置守卫:pushUrl 前检查登录态
最朴素的想法:每次跳页面前,先看看用户登没登录,没登录就拦住。这个逻辑放在哪?最直接的做法是封装一个方法,所有路由跳转都走这个口子。
import { router } from '@kit.ArkUI';
export class RouteGuard {
private static isLoggedIn(): boolean {
let token = AppStorage.get<string>('userToken');
return token !== undefined && token.length > 0;
}
public static pushWithAuth(url: string, params?: Record<string, string>): void {
if (!RouteGuard.isLoggedIn()) {
AppStorage.setOrCreate('pendingRoute', url);
if (params !== undefined) {
AppStorage.setOrCreate('pendingParams', JSON.stringify(params));
}
router.pushUrl({ url: 'pages/LoginPage' });
return;
}
let options: Record<string, string> = params !== undefined ? params : {};
router.pushUrl({ url: url, params: options });
}
}
调用时一行搞定:
RouteGuard.pushWithAuth('pages/ProfilePage');
关键是:团队里所有人必须走这个方法跳转,谁要是直接 router.pushUrl 绕过去了,守卫形同虚设。这个靠规范约束,不靠技术强制。后面说的中间件模式能缓解这个问题。
二、目标页暂存模式:未登录记下 target,登录后自动跳
光拦住不够。用户本来想看"我的"页面,被拦到登录页,登录完了就停在那?那体验太差了。正确做法是:记住用户本来要去哪,登录成功后自动跳过去。
思路很简单,用 AppStorage 存一个 pendingRoute,登录成功后检查这个值,有就跳,没有就回首页。
// LoginPage.ets
@Entry
@Component
struct LoginPage {
@State inputPhone: string = '';
@StorageLink('pendingRoute') pendingRoute: string = '';
@StorageLink('pendingParams') pendingParams: string = '';
build() {
Column() {
TextInput({ placeholder: '请输入手机号' })
.onChange((value: string) => {
this.inputPhone = value;
})
Button('登录')
.onClick(() => {
this.doLogin();
})
}
}
private doLogin(): void {
// 模拟登录成功,存储 token
AppStorage.setOrCreate('userToken', 'mock_token_123');
// 检查是否有暂存的目标页
if (this.pendingRoute.length > 0) {
let targetUrl: string = this.pendingRoute;
let paramStr: string = this.pendingParams;
// 清除暂存
AppStorage.setOrCreate('pendingRoute', '');
AppStorage.setOrCreate('pendingParams', '');
// 跳转到目标页
if (paramStr.length > 0) {
let params: Record<string, string> = JSON.parse(paramStr) as Record<string, string>;
router.pushUrl({ url: targetUrl, params: params });
} else {
router.pushUrl({ url: targetUrl });
}
} else {
router.replaceUrl({ url: 'pages/Index' });
}
}
}
这里有个细节:用完 pendingRoute 之后一定要清掉,不然下次登录还会跳到老页面。我见过有人忘了清,导致用户退出登录再重新登录时被跳到一个莫名其妙的页面,排查半天。
另一个细节是参数也要暂存。有些页面是要带参数跳转的,比如 pages/OrderDetail?id=123,光记 URL 不记参数,跳过去就是空白页。
三、onBackPress 拦截:返回时检查未保存数据
返回拦截和登录拦截是两码事,但都属于路由守卫的范畴。常见场景:表单编辑页,用户填了一半点返回,你不得问一句"确定不保存就走?"
在 @Entry 页面里,用 onBackPress 生命周期:
@Entry
@Component
struct EditPage {
@State hasUnsavedData: boolean = false;
@State draftContent: string = '';
build() {
Column() {
TextArea({ placeholder: '请输入内容' })
.onChange((value: string) => {
this.draftContent = value;
this.hasUnsavedData = value.length > 0;
})
}
}
onBackPress(): boolean {
if (!this.hasUnsavedData) {
return false; // 没有未保存数据,正常返回
}
// 有未保存数据,弹确认框
this.getUIContext().getPromptAction().showDialog({
message: '有未保存的内容,确定要离开吗?',
buttons: [
{ text: '留下编辑', color: '#007DFF' },
{ text: '不保存离开', color: '#FF0000' }
]
}).then((data: ShowDialogSuccessResponse) => {
if (data.index === 1) {
this.hasUnsavedData = false;
router.back();
}
});
return true; // 拦截返回事件
}
}
onBackPress 返回 true 表示消费了返回事件,系统不再执行默认返回逻辑;返回 false 则放行。这个设计跟 Android 的 onBackPressed 是一个路子。
如果你用的是 Navigation + NavDestination 体系,那拦截方式不一样,用 NavDestination 的 onBackPressed:
@Component
struct EditDestination {
@State hasUnsavedData: boolean = false;
pathStack: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Column() {
TextArea({ placeholder: '输入内容' })
.onChange((value: string) => {
this.hasUnsavedData = value.length > 0;
})
}
}
.onBackPressed(() => {
if (this.hasUnsavedData) {
// 弹窗确认逻辑
return true; // 拦截
}
return false; // 放行
})
.onReady((context: NavDestinationContext) => {
this.pathStack = context.pathStack;
})
}
}
注意区分:onBackPress 是 @Entry 页面的,onBackPressed 是 NavDestination 的,别搞混了。我见过有人在 NavDestination 里写 onBackPress,怎么都不生效,查了半天才发现用错 API。
还有一个坑:onBackPressed 能拦截系统侧滑和标题栏返回按钮,但拦不住 pathStack.pop() 的代码调用。如果团队里有人直接调 pop() 绕过了,那拦截就没用了。所以弹窗确认之后的"离开"操作,推荐用 pathStack.pop() 执行,而不是靠系统默认行为。
四、登录态持久化:AppStorage + PersistentStorage
上面几节都依赖"判断是否登录"这个动作,那登录态本身怎么存?
AppStorage 是内存级的,应用一杀就没了。PersistentStorage 才是磁盘持久化的。正确做法是:PersistentStorage 作为源头,AppStorage 作为运行时快照。
// EntryAbility.ets 的 onWindowStageCreate 中初始化
onWindowStageCreate(windowStage: window.WindowStage): void {
// 声明持久化 key,如果磁盘有值就同步到 AppStorage,没有就用默认值
PersistentStorage.persistProp('userToken', '');
PersistentStorage.persistProp('userRole', '');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'EntryAbility', 'loadContent failed');
return;
}
});
}
之后在代码里直接用 AppStorage.get 或 @StorageLink 读写就行,PersistentStorage 会自动同步:
// 登录成功后
AppStorage.setOrCreate('userToken', tokenValue);
AppStorage.setOrCreate('userRole', 'admin');
// 退出登录
AppStorage.setOrCreate('userToken', '');
AppStorage.setOrCreate('userRole', '');
有个大坑要提:别用 PersistentStorage.deleteProp 来清除登录态。这方法会把 key 从持久化存储里删掉,但 AppStorage 里的值可能还残留。正确做法是设为空字符串或默认值,保持 key 存在。
另外,敏感信息(比如 token)存 PersistentStorage 是明文存磁盘的,安全性一般。生产环境建议用 @ohos.data.preferences 配合加密,或者把 token 存 HUKS 管理的密钥区。不过对大部分应用来说,PersistentStorage 够用,先跑通再说。
五、权限路由表:不同角色看到的页面不一样
B 端应用常见需求:管理员能看设置页,普通员工看不到。这就需要一份"权限路由表"。
定义一个路由表结构:
export interface RouteConfig {
name: string;
path: string;
requiredRole: string; // 'all' 表示不限制
}
export class RouteTable {
private static routes: Array<RouteConfig> = [
{ name: '首页', path: 'pages/Index', requiredRole: 'all' },
{ name: '个人中心', path: 'pages/Profile', requiredRole: 'all' },
{ name: '订单列表', path: 'pages/OrderList', requiredRole: 'user' },
{ name: '管理后台', path: 'pages/Admin', requiredRole: 'admin' },
{ name: '数据统计', path: 'pages/Statistics', requiredRole: 'admin' }
];
public static canAccess(pagePath: string): boolean {
let userRole = AppStorage.get<string>('userRole');
if (userRole === undefined || userRole.length === 0) {
return false;
}
for (let i = 0; i < RouteTable.routes.length; i++) {
let route = RouteTable.routes[i];
if (route.path === pagePath) {
if (route.requiredRole === 'all') {
return true;
}
return userRole === route.requiredRole;
}
}
return false;
}
public static getAccessibleRoutes(): Array<RouteConfig> {
let userRole = AppStorage.get<string>('userRole');
if (userRole === undefined || userRole.length === 0) {
return [];
}
let result: Array<RouteConfig> = [];
for (let i = 0; i < RouteTable.routes.length; i++) {
let route = RouteTable.routes[i];
if (route.requiredRole === 'all' || userRole === route.requiredRole) {
result.push(route);
}
}
return result;
}
}
在导航或菜单渲染时,用 getAccessibleRoutes() 过滤可见项。跳转时用 canAccess() 做二次校验:
if (RouteTable.canAccess('pages/Admin')) {
RouteGuard.pushWithAuth('pages/Admin');
} else {
this.getUIContext().getPromptAction().showToast({ message: '无权访问该页面' });
}
这个方案是客户端侧的前端权限控制,不能替代后端鉴权。服务端接口该验 token 验 token,该验角色验角色,前端路由表只是优化体验,避免用户看到一个没权限的页面再被接口弹回来。
六、NavPathStack 守卫:setInterception 回调拦截
如果你用的是 Navigation 体系(HarmonyOS NEXT 主推),NavPathStack 提供了 setInterception 方法,可以在路由切换前拦截。这是最接近 vue-router beforeEach 的能力。
@Entry
@Component
struct MainPage {
pathStack: NavPathStack = new NavPathStack();
aboutToAppear(): void {
this.pathStack.setInterception({
willShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar,
operation: NavigationOperation, isAnimated: boolean) => {
// 从 to 获取目标页名称
let targetName = '';
if (to instanceof NavDestinationContext) {
targetName = to.pathInfo.name;
}
// 检查是否需要登录
if (this.needAuth(targetName)) {
let token = AppStorage.get<string>('userToken');
if (token === undefined || token.length === 0) {
AppStorage.setOrCreate('pendingNavName', targetName);
this.pathStack.pushPathByName('LoginPage', null, false);
return; // 拦截原跳转
}
}
},
didShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar,
operation: NavigationOperation, isAnimated: boolean) => {
// 页面已显示后的回调,可用于埋点
}
});
}
private needAuth(pageName: string): boolean {
// 定义需要登录的页面列表
let authPages: Array<string> = ['ProfilePage', 'OrderPage', 'AdminPage'];
for (let i = 0; i < authPages.length; i++) {
if (authPages[i] === pageName) {
return true;
}
}
return false;
}
build() {
Navigation(this.pathStack) {
Column() {
Text('首页内容')
}
}
.navDestination(this.pageMap);
}
@Builder
pageMap(name: string) {
if (name === 'LoginPage') {
LoginPage();
} else if (name === 'ProfilePage') {
ProfilePage();
}
}
}
setInterception 有三个回调:
willShow:页面即将显示前触发,可以做拦截didShow:页面已显示后触发- 实际开发中最常用的是
willShow,配合needAuth判断逻辑
注意一点:setInterception 只能拦截用户操作触发的导航(侧滑、系统返回),代码里直接调用 pushPathByName 也会触发 willShow,但在 willShow 里再调用 pushPathByName 跳登录页时要注意避免循环拦截。登录页本身不能被 needAuth 判定为需要登录,否则就死循环了。
七、深链拦截:从外部跳转进来时的权限检查
HarmonyOS 支持 App Linking 和 Deep Link,用户可以从浏览器、短信等外部场景直接跳到你应用的某个页面。这种场景下的权限检查很多人漏掉了。
深链的入口在 UIAbility 的 onNewWant 和 onCreate 中:
// EntryAbility.ets
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.handleDeepLink(want);
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.handleDeepLink(want);
}
private handleDeepLink(want: Want): void {
let uri = want.parameters !== undefined ? want.parameters['uri'] : undefined;
if (uri === undefined || typeof uri !== 'string') {
return;
}
// 解析深链目标
let targetPage = this.parseDeepLink(uri);
// 检查目标页是否需要登录
let token = AppStorage.get<string>('userToken');
if (this.isProtectedPage(targetPage) && (token === undefined || token.length === 0)) {
// 未登录,暂存目标,登录后跳转
AppStorage.setOrCreate('deepLinkTarget', targetPage);
AppStorage.setOrCreate('deepLinkUri', uri);
} else {
// 已登录或无需登录,直接跳转
AppStorage.setOrCreate('deepLinkTarget', targetPage);
}
}
private parseDeepLink(uri: string): string {
// 简单解析,实际项目按业务规则处理
if (uri.indexOf('/order/') >= 0) {
return 'OrderDetailPage';
}
if (uri.indexOf('/profile') >= 0) {
return 'ProfilePage';
}
return 'Index';
}
private isProtectedPage(pageName: string): boolean {
let protectedPages: Array<string> = ['OrderDetailPage', 'ProfilePage', 'AdminPage'];
for (let i = 0; i < protectedPages.length; i++) {
if (protectedPages[i] === pageName) {
return true;
}
}
return false;
}
onWindowStageCreate(windowStage: window.WindowStage): void {
PersistentStorage.persistProp('userToken', '');
PersistentStorage.persistProp('userRole', '');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'EntryAbility', 'loadContent failed');
}
});
}
}
深链拦截的要点:
onCreate是应用冷启动时触发,onNewWant是应用已存在时再次被拉起触发,两个都要处理- 深链跳转进来时,页面可能还没加载完,
AppStorage的值可能还没从PersistentStorage同步过来,所以初始化顺序很重要——persistProp要在loadContent之前调用 - 深链带来的 URI 可能包含敏感参数,解析时要过滤和校验,别把原始 URI 直接当路由路径用了
八、路由中间件模式:封装 checkAndNavigate 通用方法
前面说的方法各有各的调用方式,维护起来比较散。更工程化的做法是封装一个"路由中间件",所有跳转统一走一个入口,中间件链式执行检查逻辑。
export interface MiddlewareResult {
allowed: boolean;
redirectUrl: string;
}
export interface RouteMiddleware {
name: string;
check(targetUrl: string, params: Record<string, string>): MiddlewareResult;
}
export class RouteMiddlewareChain {
private static middlewares: Array<RouteMiddleware> = [];
private static targetPage: string = '';
private static targetParams: Record<string, string> = new Record<string, string>();
public static register(middleware: RouteMiddleware): void {
RouteMiddlewareChain.middlewares.push(middleware);
}
public static checkAndNavigate(url: string, params?: Record<string, string>): void {
let currentParams: Record<string, string> = params !== undefined ? params : new Record<string, string>();
// 依次执行中间件
for (let i = 0; i < RouteMiddlewareChain.middlewares.length; i++) {
let middleware = RouteMiddlewareChain.middlewares[i];
let result = middleware.check(url, currentParams);
if (!result.allowed) {
// 被拦截,跳转重定向页
if (result.redirectUrl.length > 0) {
AppStorage.setOrCreate('pendingRoute', url);
router.pushUrl({ url: result.redirectUrl });
}
return;
}
}
// 全部通过,执行跳转
router.pushUrl({ url: url, params: currentParams });
}
}
然后定义具体的中间件:
// 登录检查中间件
export class AuthMiddleware implements RouteMiddleware {
name: string = 'AuthMiddleware';
private protectedPages: Array<string> = ['ProfilePage', 'OrderPage', 'SettingsPage'];
check(targetUrl: string, params: Record<string, string>): MiddlewareResult {
let isProtected = false;
for (let i = 0; i < this.protectedPages.length; i++) {
if (targetUrl.indexOf(this.protectedPages[i]) >= 0) {
isProtected = true;
break;
}
}
if (!isProtected) {
return { allowed: true, redirectUrl: '' };
}
let token = AppStorage.get<string>('userToken');
if (token !== undefined && token.length > 0) {
return { allowed: true, redirectUrl: '' };
}
return { allowed: false, redirectUrl: 'pages/LoginPage' };
}
}
// 角色权限中间件
export class RoleMiddleware implements RouteMiddleware {
name: string = 'RoleMiddleware';
private adminPages: Array<string> = ['AdminPage', 'StatisticsPage'];
check(targetUrl: string, params: Record<string, string>): MiddlewareResult {
let needAdmin = false;
for (let i = 0; i < this.adminPages.length; i++) {
if (targetUrl.indexOf(this.adminPages[i]) >= 0) {
needAdmin = true;
break;
}
}
if (!needAdmin) {
return { allowed: true, redirectUrl: '' };
}
let role = AppStorage.get<string>('userRole');
if (role === 'admin') {
return { allowed: true, redirectUrl: '' };
}
return { allowed: false, redirectUrl: 'pages/NoPermissionPage' };
}
}
注册中间件,在应用启动时执行一次:
// 应用初始化时
RouteMiddlewareChain.register(new AuthMiddleware());
RouteMiddlewareChain.register(new RoleMiddleware());
之后所有路由跳转用一行:
RouteMiddlewareChain.checkAndNavigate('pages/AdminPage');
中间件模式的好处是扩展方便。以后要加"VIP 检查"、“版本检查”、“地区限制”,只要写一个新的 Middleware 注册进去就行,不用改已有代码。符合开闭原则。
但也别过度设计。如果你的应用总共就三四个页面、一种角色,搞一整套中间件链纯属折腾。根据项目规模选方案,够用就行。
九、页面级鉴权:aboutToAppear 中检查权限
上面说的都是"跳转前拦截",但有时候你在页面内部也需要做鉴权——比如页面已经打开了,但发现权限不对,需要主动退出。
这种场景用 aboutToAppear 生命周期:
@Component
struct AdminPage {
@StorageLink('userRole') userRole: string = '';
pathStack: NavPathStack = new NavPathStack();
aboutToAppear(): void {
if (this.userRole !== 'admin') {
// 无权限,弹提示后跳走
this.getUIContext().getPromptAction().showToast({ message: '无管理员权限' });
this.pathStack.pop();
}
}
build() {
NavDestination() {
Column() {
Text('管理后台')
.fontSize(24)
}
}
.onReady((context: NavDestinationContext) => {
this.pathStack = context.pathStack;
})
}
}
页面级鉴权是最后一道防线。即使前置拦截有漏洞(比如有人绕过了中间件直接 pushPathByName),页面内部的 aboutToAppear 还能兜底。
但有个问题:用户会看到页面"闪一下"——页面先渲染出来,然后 aboutToAppear 里发现没权限又跳走。体验不好。优化方案是加个 loading 态:
@Component
struct AdminPage {
@StorageLink('userRole') userRole: string = '';
@State isChecking: boolean = true;
@State hasPermission: boolean = false;
pathStack: NavPathStack = new NavPathStack();
aboutToAppear(): void {
this.hasPermission = this.userRole === 'admin';
this.isChecking = false;
if (!this.hasPermission) {
this.getUIContext().getPromptAction().showToast({ message: '无管理员权限' });
this.pathStack.pop();
}
}
build() {
NavDestination() {
if (this.isChecking) {
LoadingProgress()
} else if (this.hasPermission) {
Column() {
Text('管理后台')
.fontSize(24)
}
}
}
.onReady((context: NavDestinationContext) => {
this.pathStack = context.pathStack;
})
}
}
这样在权限检查完成前显示 loading,不会闪烁。
十、路由重定向:未登录访问受保护页 -> 登录 -> 回原页
这是最经典的流程,也是前文各节内容的串联。把完整链路画清楚:
- 用户点击"个人中心" -> 前置守卫检测未登录 -> 暂存
pendingRoute = 'ProfilePage'-> 跳转登录页 - 用户在登录页完成登录 -> 检测到
pendingRoute非空 -> 跳转到ProfilePage-> 清除pendingRoute - 如果用户在登录页点了返回(没登录) ->
pendingRoute也得清掉,否则下次登录会误跳
完整实现:
// RouteManager.ets - 统一路由管理器
export class RouteManager {
private static instance: NavPathStack = new NavPathStack();
public static bind(stack: NavPathStack): void {
RouteManager.instance = stack;
RouteManager.setupInterception();
}
private static setupInterception(): void {
RouteManager.instance.setInterception({
willShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar,
operation: NavigationOperation, isAnimated: boolean) => {
let targetName = '';
if (to instanceof NavDestinationContext) {
targetName = to.pathInfo.name;
}
if (targetName.length === 0) {
return;
}
let token = AppStorage.get<string>('userToken');
let isLoggedIn = token !== undefined && token.length > 0;
if (!isLoggedIn && RouteManager.isProtectedPage(targetName)) {
// 暂存目标页
AppStorage.setOrCreate('pendingRoute', targetName);
// 取消原跳转,跳登录
RouteManager.instance.pushPathByName('LoginPage', null, false);
}
}
});
}
private static isProtectedPage(name: string): boolean {
let protectedList: Array<string> = ['ProfilePage', 'OrderPage', 'SettingsPage'];
for (let i = 0; i < protectedList.length; i++) {
if (protectedList[i] === name) {
return true;
}
}
return false;
}
// 登录成功后调用
public static onLoginSuccess(): void {
let pending = AppStorage.get<string>('pendingRoute');
if (pending !== undefined && pending.length > 0) {
let target = pending;
AppStorage.setOrCreate('pendingRoute', '');
RouteManager.instance.pushPathByName(target, null);
} else {
RouteManager.instance.pushPathByName('Index', null);
}
}
// 退出登录后调用
public static onLogout(): void {
AppStorage.setOrCreate('userToken', '');
AppStorage.setOrCreate('userRole', '');
AppStorage.setOrCreate('pendingRoute', '');
RouteManager.instance.clear();
}
}
在登录页的登录按钮回调中:
Button('登录')
.onClick(() => {
// 登录逻辑...
AppStorage.setOrCreate('userToken', 'new_token');
RouteManager.onLoginSuccess();
})
退出登录时:
Button('退出登录')
.onClick(() => {
RouteManager.onLogout();
})
这样整个重定向链路就通了。关键点在 onLoginSuccess 里先检查 pendingRoute,有就跳过去,没有就去首页。onLogout 里务必清掉 pendingRoute,防止残留脏数据。
常见坑与实践建议
| 问题 | 表现 | 原因 | 解决方案 |
|---|---|---|---|
| PersistentStorage 未初始化就读 | 冷启动首次判断登录态失败 | persistProp 在 loadContent 之后调用 | 把 persistProp 放在 loadContent 之前 |
| setInterception 循环拦截 | 登录页也被 willShow 拦截,死循环 | needAuth 没排除登录页 | 登录页加入白名单,不被拦截 |
| onBackPress 不生效 | 在 NavDestination 里写 onBackPress 无反应 | @Entry 页面用 onBackPress,NavDestination 用 onBackPressed | 区分使用场景,NavDestination 里用 onBackPressed |
| pop() 绕过 onBackPressed | 代码调 pathStack.pop() 时返回拦截不触发 | onBackPressed 只拦截系统返回和侧滑 | 统一封装 pop 方法,在方法内加检查 |
| pendingRoute 未清除 | 退出登录再登录,跳到旧页面 | 退出时没清 pendingRoute | onLogout 里主动清除所有暂存路由 |
| 深链参数丢失 | 从外部进来后跳转缺参数 | URI 解析不完整,只取了页面名没取参数 | parseDeepLink 要完整解析并暂存参数 |
| 页面闪烁 | 无权限页面先渲染再跳走 | aboutToAppear 是页面已创建后才执行 | 加 isChecking 状态,检查前显示 loading |
| 多角色权限遗漏 | 普通用户看到管理员入口 | 菜单渲染没过滤 | 用 RouteTable.getAccessibleRoutes() 过滤菜单项 |
| token 过期未处理 | 本地有 token 但服务端已过期 | 只检查有无 token,没校验有效期 | 加 token 过期时间判断或服务端 401 时清 token |
| AppStorage 并发写入 | 多处同时写 userToken 导致状态不一致 | AppStorage 是同步的但无事务 | 限定写入入口,统一通过 RouteManager 操作 |
总结
HarmonyOS 的路由守卫不像 Web 端有一个统一的 beforeEach 钩子,但它给了你足够的积木来拼。setInterception 做全局拦截,onBackPressed 做返回拦截,aboutToAppear 做页面级兜底,PersistentStorage 做持久化,AppStorage 做运行时共享。把这些拼起来,就是一套完整的路由守卫体系。
核心原则就一条:所有路由操作走统一入口,不给人绕过守卫的机会。中间件模式也好、RouteManager 也好,都是在解决这个问题。剩下的就是根据业务复杂度选方案——简单项目一个 AuthMiddleware 就够了,复杂项目才需要完整的中间件链。
别过度设计,也别裸奔。找到你项目对应的那个度,写出来的代码才好维护。
更多推荐
所有评论(0)