路由守卫
简介
路由守卫为 Nylo Website 提供导航中间件功能。它们拦截路由转换,允许你控制用户是否可以访问某个页面、将其重定向到其他页面,或修改传递给路由的数据。
常见用例包括:
- 身份验证检查 -- 将未认证用户重定向到登录页面
- 基于角色的访问控制 -- 限制页面仅供管理员用户访问
- 数据验证 -- 确保导航前所需数据已存在
- 数据补充 -- 向路由附加额外数据
守卫按顺序在导航发生之前执行。如果任何守卫返回 handled,导航将停止(执行重定向或中止操作)。
创建路由守卫
使用 Metro CLI 创建路由守卫:
metro make:route_guard auth
这将生成一个守卫文件:
import 'package:nylo_framework/nylo_framework.dart';
class AuthRouteGuard extends NyRouteGuard {
AuthRouteGuard();
@override
Future<GuardResult> onBefore(RouteContext context) async {
// Add your guard logic here
return next();
}
}
守卫生命周期
每个路由守卫都有三个生命周期方法:
onBefore
在导航发生之前调用。在这里检查条件并决定是允许、重定向还是中止导航。
@override
Future<GuardResult> onBefore(RouteContext context) async {
bool isLoggedIn = await Auth.isAuthenticated();
if (!isLoggedIn) {
return redirect(HomePage.path);
}
return next();
}
返回值:
next()-- 继续执行下一个守卫或导航到该路由redirect(path)-- 重定向到不同的路由abort()-- 完全取消导航
onAfter
在导航成功之后调用。用于分析统计、日志记录或导航后的副作用操作。
@override
Future<void> onAfter(RouteContext context) async {
// Log page view
Analytics.trackPageView(context.routeName);
}
onLeave
当用户离开某个路由时调用。返回 false 可以阻止用户离开。
@override
Future<bool> onLeave(RouteContext context) async {
if (hasUnsavedChanges) {
// Show confirmation dialog
return await showConfirmDialog();
}
return true; // Allow leaving
}
RouteContext
RouteContext 对象被传递给所有守卫生命周期方法,包含有关导航的信息:
| 属性 | 类型 | 描述 |
|---|---|---|
context |
BuildContext? |
当前的构建上下文 |
data |
dynamic |
传递给路由的数据 |
queryParameters |
Map<String, String> |
URL 查询参数 |
routeName |
String |
目标路由的名称/路径 |
originalRouteName |
String? |
转换前的原始路由名称 |
@override
Future<GuardResult> onBefore(RouteContext context) async {
// Access route information
String route = context.routeName;
dynamic routeData = context.data;
Map<String, String> params = context.queryParameters;
return next();
}
转换路由上下文
创建具有不同数据的副本:
// Change the data type
RouteContext<User> userContext = context.withData<User>(currentUser);
// Copy with modified fields
RouteContext updated = context.copyWith(
data: enrichedData,
queryParameters: {"tab": "settings"},
);
守卫操作
next
继续执行链中的下一个守卫,如果这是最后一个守卫则导航到该路由:
return next();
redirect
将用户重定向到不同的路由:
return redirect(LoginPage.path);
附带额外选项:
return redirect(
LoginPage.path,
data: {"returnTo": context.routeName},
navigationType: NavigationType.pushReplace,
queryParameters: {"source": "guard"},
);
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
path |
Object |
必填 | 路由路径字符串或 RouteView |
data |
dynamic |
null | 传递给重定向路由的数据 |
queryParameters |
Map<String, dynamic>? |
null | 查询参数 |
navigationType |
NavigationType |
pushReplace |
导航方式 |
result |
dynamic |
null | 返回结果 |
removeUntilPredicate |
Function? |
null | 路由移除谓词 |
transitionType |
TransitionType? |
null | 页面过渡类型 |
onPop |
Function(dynamic)? |
null | 弹出时的回调 |
abort
取消导航而不进行重定向。用户停留在当前页面:
return abort();
setData
修改将传递给后续守卫和目标路由的数据:
@override
Future<GuardResult> onBefore(RouteContext context) async {
User user = await fetchUser();
// Enrich the route data
setData({"user": user, "originalData": context.data});
return next();
}
将守卫应用于路由
在路由文件中为单个路由添加守卫:
appRouter() => nyRoutes((router) {
router.route(
HomePage.path,
(_) => HomePage(),
).initialRoute();
// Add a single guard
router.route(
ProfilePage.path,
(_) => ProfilePage(),
routeGuards: [AuthRouteGuard()],
);
// Add multiple guards (executed in order)
router.route(
AdminPage.path,
(_) => AdminPage(),
routeGuards: [AuthRouteGuard(), AdminRoleGuard()],
);
});
分组守卫
使用路由分组将守卫同时应用于多个路由:
appRouter() => nyRoutes((router) {
router.route(HomePage.path, (_) => HomePage()).initialRoute();
// All routes in this group require authentication
router.group(() {
return {
'prefix': '/dashboard',
'route_guards': [AuthRouteGuard()],
};
}, (router) {
router.route(DashboardPage.path, (_) => DashboardPage());
router.route(SettingsPage.path, (_) => SettingsPage());
router.route(ProfilePage.path, (_) => ProfilePage());
});
});
守卫组合
Nylo Website 提供了将守卫组合在一起的工具,以实现可复用的模式。
GuardStack
将多个守卫合并为一个可复用的守卫:
final protectedRoute = GuardStack([
AuthRouteGuard(),
VerifyEmailGuard(),
TwoFactorGuard(),
]);
// Use the stack on a route
router.route(
SecurePage.path,
(_) => SecurePage(),
routeGuards: [protectedRoute],
);
GuardStack 按顺序执行守卫。如果任何守卫返回 handled,剩余的守卫将被跳过。
ConditionalGuard
仅在条件为真时应用守卫:
router.route(
BetaPage.path,
(_) => BetaPage(),
routeGuards: [
ConditionalGuard(
condition: (context) => context.queryParameters.containsKey("beta"),
guard: BetaAccessGuard(),
),
],
);
如果条件返回 false,守卫将被跳过,导航继续进行。
ParameterizedGuard
创建接受配置参数的守卫:
class RoleGuard extends ParameterizedGuard<List<String>> {
RoleGuard(super.params); // params = allowed roles
@override
Future<GuardResult> onBefore(RouteContext context) async {
User? user = await Auth.user<User>();
if (user == null || !params.contains(user.role)) {
return redirect(UnauthorizedPage.path);
}
return next();
}
}
// Usage
router.route(
AdminPage.path,
(_) => AdminPage(),
routeGuards: [RoleGuard(["admin", "super_admin"])],
);
示例
身份验证守卫
class AuthRouteGuard extends NyRouteGuard {
AuthRouteGuard();
@override
Future<GuardResult> onBefore(RouteContext context) async {
bool isAuthenticated = await Auth.isAuthenticated();
if (!isAuthenticated) {
return redirect(HomePage.path);
}
return next();
}
}
带参数的订阅守卫
class SubscriptionGuard extends ParameterizedGuard<List<String>> {
SubscriptionGuard(super.params);
@override
Future<GuardResult> onBefore(RouteContext context) async {
User? user = await Auth.user<User>();
bool hasAccess = params.any((plan) => user?.subscription == plan);
if (!hasAccess) {
return redirect(UpgradePage.path, data: {"plans": params});
}
setData({"user": user});
return next();
}
}
// Require premium or pro subscription
router.route(
PremiumPage.path,
(_) => PremiumPage(),
routeGuards: [
AuthRouteGuard(),
SubscriptionGuard(["premium", "pro"]),
],
);
日志守卫
class LoggingGuard extends NyRouteGuard {
LoggingGuard();
@override
Future<GuardResult> onBefore(RouteContext context) async {
print("Navigating to: ${context.routeName}");
return next();
}
@override
Future<void> onAfter(RouteContext context) async {
print("Arrived at: ${context.routeName}");
}
}