路由系统:基本路由、路由参数、路由命名、路由分组、路由中间件
路由,说白了就是 Laravel 应用的「门卫」。用户访问哪个 URL,该走哪条路,都由它说了算。我刚开始学 Laravel 时,觉得路由不就是个映射表嘛,后来才发现,这里面门道还挺深。
今天咱们就把路由系统彻底聊透。从最基础的路由定义,到参数传递、命名、分组,再到中间件拦截,一条龙讲清楚。
基本路由:最简单的入口
先看个最基础的例子。在 routes/web.php 里写一行:
Route::get('/hello', function () {
return 'Hello, Laravel!';
});
这就定义了一个 GET 请求的路由。用户访问 http://your-app.com/hello,就能看到「Hello, Laravel!」。
除了 get,Laravel 还支持 post、put、patch、delete 等 HTTP 方法。比如:
Route::post('/submit', function () {
return '表单已提交';
});
如果你想一个路由同时响应多种方法,可以用 match 或 any:
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 怎么变,代码都不用改。这就是命名的威力。
路由分组:组织路由的艺术
当路由多起来以后,你会发现很多路由有共同点。比如都带 /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/profile,profile要写在前面。 - 命名重复:路由名不能重复。重复了后面的会覆盖前面的。我习惯用「资源名.动作」的命名方式,比如
user.show、post.edit,这样不容易冲突。
好了,路由系统就聊到这儿。说白了,路由就是应用的「地图」,把每个 URL 映射到对应的处理逻辑。掌握好基本路由、参数、命名、分组和中间件,你就能搭建出清晰、可维护的路由结构。
下一章,咱们聊聊控制器。路由只是入口,真正的业务逻辑都在控制器里。到时候见。