路由系统:基本路由、路由参数、路由命名、路由分组、路由中间件

路由,说白了就是 Laravel 应用的「门卫」。用户访问哪个 URL,该走哪条路,都由它说了算。我刚开始学 Laravel 时,觉得路由不就是个映射表嘛,后来才发现,这里面门道还挺深。

今天咱们就把路由系统彻底聊透。从最基础的路由定义,到参数传递、命名、分组,再到中间件拦截,一条龙讲清楚。

基本路由:最简单的入口

先看个最基础的例子。在 routes/web.php 里写一行:

Route::get('/hello', function () {
    return 'Hello, Laravel!';
});

这就定义了一个 GET 请求的路由。用户访问 http://your-app.com/hello,就能看到「Hello, Laravel!」。

除了 get,Laravel 还支持 postputpatchdelete 等 HTTP 方法。比如:

Route::post('/submit', function () {
    return '表单已提交';
});

如果你想一个路由同时响应多种方法,可以用 matchany

Route::match(['get', 'post'], '/test', function () {
    return 'GET 或 POST 都能访问';
});

Route::any('/any', function () {
    return '任何方法都能访问';
});
我的习惯:能明确指定方法的,就别用 any。我在项目中见过有人图省事全用 any,结果 POST 请求也能拿到缓存数据,排查了半天。明确约束,能少踩很多坑。

路由参数:动态 URL 的基石

实际项目中,URL 往往是动态的。比如用户个人主页:/user/1/user/2。这时候就需要路由参数。

Route::get('/user/{id}', function ($id) {
    return '用户 ID:' . $id;
});

花括号里的 {id} 就是参数。访问 /user/42$id 的值就是 42。

参数还可以有多个:

Route::get('/post/{year}/{month}/{slug}', function ($year, $month, $slug) {
    return "{$year}年{$month}月的文章:{$slug}";
});

有时候参数是可选的。比如文章详情页,可能带 slug,也可能不带:

Route::get('/article/{id}/{slug?}', function ($id, $slug = null) {
    if ($slug) {
        return "文章 {$id},别名:{$slug}";
    }
    return "文章 {$id}";
});

注意,可选参数必须放在最后,而且要给默认值。这是 PHP 的语法规则,也是 Laravel 的要求。

我曾经踩过的坑:参数名和变量名搞混了。比如路由里写 {user_id},闭包里却用 $userId。Laravel 会报错,但错误信息不太直观。我的建议是:参数名和变量名保持一致,都用下划线或驼峰,别混着来。

路由命名:给路由起个名字

你有没有遇到过这种情况?视图里写死了 /user/profile,后来路由改成 /member/profile,所有地方都得改一遍。累不累?

路由命名就是解决这个问题的。给路由起个名字,以后都用名字引用:

Route::get('/user/profile', function () {
    return '个人中心';
})->name('profile');

在视图或控制器里,用 route('profile') 就能生成 URL:

<a href="{{ route('profile') }}">个人中心</a>

如果路由有参数,命名路由也能处理:

Route::get('/user/{id}', function ($id) {
    return '用户:' . $id;
})->name('user.show');

// 生成 URL:/user/42
$url = route('user.show', ['id' => 42]);

命名还有个好处:重定向时特别方便。比如用户未登录时跳转到登录页:

return redirect()->route('login');

你看,不管登录页的 URL 怎么变,代码都不用改。这就是命名的威力。

我的建议:所有需要在前端或控制器里引用的路由,都给它命名。别偷懒。我见过一个项目,300 多个路由只有十几个命名,后来改 URL 结构时,改到崩溃。命名花不了几秒钟,但能省下几小时。

路由分组:组织路由的艺术

当路由多起来以后,你会发现很多路由有共同点。比如都带 /admin 前缀,或者都需要登录。这时候分组就派上用场了。

先看前缀分组:

Route::prefix('admin')->group(function () {
    Route::get('/dashboard', function () {
        return '管理后台首页';
    });
    Route::get('/users', function () {
        return '用户管理';
    });
});

这样,/admin/dashboard/admin/users 就都定义好了。不用在每个路由前写 admin/,清爽多了。

再看命名空间分组。如果你把控制器放在子目录里:

Route::namespace('Admin')->group(function () {
    Route::get('/dashboard', 'DashboardController@index');
    Route::get('/users', 'UserController@index');
});

这样 Laravel 会自动去 App\Http\Controllers\Admin 下找控制器。

还有子域名分组。比如你的应用有 admin.example.com

Route::domain('admin.example.com')->group(function () {
    Route::get('/', function () {
        return '管理后台';
    });
});

分组还能嵌套。比如后台管理模块,既有前缀又有中间件:

Route::prefix('admin')->middleware('auth')->group(function () {
    Route::get('/dashboard', function () {
        return '需要登录才能访问';
    });
});
我的经验:分组别套太深。超过三层,可读性就下降了。我一般控制在两层以内。如果逻辑太复杂,不如拆成多个文件,用 Route::group() 加载。

路由中间件:请求的守门员

中间件,说白了就是在请求到达路由之前,或者响应返回之前,做点「手脚」。最常见的场景就是登录验证。

Laravel 内置了 auth 中间件,用来检查用户是否登录:

Route::get('/dashboard', function () {
    return '欢迎回来';
})->middleware('auth');

没登录的用户访问 /dashboard,会被重定向到登录页。这就是中间件干的事。

你还可以给一组路由加中间件:

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('/profile', function () {
        return '个人资料';
    });
    Route::get('/settings', function () {
        return '设置';
    });
});

这样,这两个路由都需要登录且邮箱已验证才能访问。

中间件还能接收参数。比如限制角色:

Route::get('/admin', function () {
    return '管理员专属';
})->middleware('role:admin');

在中间件里,你可以拿到 admin 这个参数,判断当前用户是否有权限。

我曾经犯过的错:中间件顺序搞反了。比如先执行 throttle(限流),再执行 auth(登录验证)。结果未登录用户也被限流了,连登录页都进不去。正确的做法是:先验证身份,再限流。中间件的执行顺序,就是你在数组里写的顺序。

避坑指南:路由常见问题

聊了这么多,最后分享几个我实际项目中踩过的坑:

  • 路由缓存问题:Laravel 的路由可以缓存,但只对控制器路由有效。闭包路由不能缓存。如果你用了 php artisan route:cache,记得检查有没有闭包路由,否则会报错。
  • 参数正则约束:有时候参数需要限定格式。比如 {id} 只能是数字:Route::get('/user/{id}', ...)->where('id', '[0-9]+')。不加约束的话,/user/abc 也能访问,可能引发意外。
  • 路由冲突:两个路由匹配同一个 URL 时,先定义的那个生效。所以,通配符路由要放在最后。比如 /user/{slug}/user/profileprofile 要写在前面。
  • 命名重复:路由名不能重复。重复了后面的会覆盖前面的。我习惯用「资源名.动作」的命名方式,比如 user.showpost.edit,这样不容易冲突。

好了,路由系统就聊到这儿。说白了,路由就是应用的「地图」,把每个 URL 映射到对应的处理逻辑。掌握好基本路由、参数、命名、分组和中间件,你就能搭建出清晰、可维护的路由结构。

下一章,咱们聊聊控制器。路由只是入口,真正的业务逻辑都在控制器里。到时候见。