CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Laravel 实战入门/课时/路由
RawBlame
课时目录12 节

Laravel 实战入门

  1. 01Laravel 概览
  2. 02目录结构
  3. 03路由
  4. 04控制器
  5. 05Blade 模板
  6. 06Eloquent ORM
  7. 07认证与授权
  8. 08验证与异常
  9. 09队列与事件
  10. 10运维与工具
  11. 11测试
  12. 12综合练习

路由

命名路由、分组与限流。

30 分钟

路由系统:从 URL 到控制器

这一课你将能做到

  1. 写出带参数、约束、命名的路由,并能用 route() 反向生成 URL
  2. 用路由分组统一配置前缀、中间件、子域名和控制器命名空间
  3. 使用路由模型绑定直接拿到模型实例,并用 route:list 排查路由问题

为什么要学

路由是 URL 与代码之间的契约。Spring Boot 里你用 @GetMapping("/orders/{id}") 把 URL 绑到方法上,Laravel 的路由文件承担完全相同的职责,但表达能力更集中:参数约束、默认值、命名、分组、模型绑定、限流、限流退避都可以在一行链式调用里声明。

真实后端项目里路由还承载两个工程问题:接口版本化(/api/v1/...)和权限分层(游客、登录用户、管理员)。掌握分组和中间件组合后,这两件事都能用统一结构表达,而不用在每个控制器方法里重复写判断。

先建立图像

请求  GET /api/v1/orders/42
                 │
                 ▼
        routes/api.php
                 │
  Route::prefix('v1')->middleware('auth:sanctum')->group(...)
                 │
        Route::get('/orders/{order}', ...)
                 │         │
                 │         └─ 路由模型绑定:{order} → Order 模型(id=42)
                 ▼
        [OrderController::class, 'show']

与 Spring Boot 对照:

LaravelSpring Boot
Route::get('/orders/{order}', ...)@GetMapping("/orders/{order}")
{order} 参数 + whereNumber()@PathVariable Long order + 正则/校验
->name('orders.show')没有内建等价物(手写路径常量)
Route::middleware(['auth'])@PreAuthorize 或过滤器
路由模型绑定需在方法内 orderRepository.findById()
php artisan route:listActuator mappings(需额外开启)

跟着做

步骤 1:基础动词路由

在 routes/api.php 中写一组资源路由:

<?php

use App\Http\Controllers\Api\OrderController;
use Illuminate\Support\Facades\Route;

Route::get('/ping', fn () => response()->json(['pong' => true]));

// 单个动词
Route::get('/orders', [OrderController::class, 'index']);
Route::post('/orders', [OrderController::class, 'store']);
Route::get('/orders/{order}', [OrderController::class, 'show']);
Route::match(['put', 'patch'], '/orders/{order}', [OrderController::class, 'update']);
Route::delete('/orders/{order}', [OrderController::class, 'destroy']);

// 等价的资源路由一行声明(7 个 RESTful 动作)
Route::apiResource('orders', OrderController::class);

apiResource 与 resource 的区别:前者不注册 create / edit(返回 HTML 表单页)这两个动作,纯 API 项目用前者。它对应 Spring 社区常见的 REST 控制器约定。

步骤 2:参数、约束与默认值

// 必填参数 + 仅允许数字
Route::get('/users/{user}/orders/{order}', function (int $user, int $order) {
    return response()->json(['user_id' => $user, 'order_id' => $order]);
})->whereNumber(['user', 'order']);

// 可选参数 + 默认值
Route::get('/reports/{type?}', function (string $type = 'daily') {
    return response()->json(['type' => $type]);
});

// 正则约束:slug 只允许小写字母、数字、短横线
Route::get('/posts/{slug}', function (string $slug) {
    return response()->json(['slug' => $slug]);
})->where('slug', '[a-z0-9\-]+');

// 全局约束:在 AppServiceProvider::boot() 中定义一次
// Route::pattern('id', '[0-9]+');

步骤 3:命名路由与反向生成 URL

Route::get('/orders/{order}/items', [OrderController::class, 'items'])
    ->name('orders.items');

// 在代码中生成 URL(不硬编码路径,重构路径时只改路由文件)
$url = route('orders.items', ['order' => 42, 'page' => 2]);
// http://127.0.0.1:8000/api/orders/42/items?page=2

// 控制器重定向到命名路由
return redirect()->route('orders.items', ['order' => 42]);

命名路由是 Laravel 比 Spring 更省事的一点:模板里的链接、重定向、校验失败回跳都用名字,路径变更时全局自动生效。

步骤 4:路由分组

真实项目的 API 通常长这样:

Route::prefix('v1')
    ->middleware('auth:sanctum')
    ->group(function () {
        // 最终路径:/api/v1/orders
        Route::get('/orders', [OrderController::class, 'index']);

        // 仅管理员可访问:中间件叠加
        Route::middleware('role:admin')->group(function () {
            Route::post('/products', [\App\Http\Controllers\Api\ProductController::class, 'store']);
        });

        // 卖家中心:再加一层前缀
        Route::prefix('seller')->group(function () {
            Route::get('/statistics', [\App\Http\Controllers\Api\SellerStatController::class, 'show']);
            // → /api/v1/seller/statistics
        });
    });

如果同一个控制器有多个方法,可以用 controller() 省略数组:

use App\Http\Controllers\Api\ProductController;

Route::controller(ProductController::class)->group(function () {
    Route::get('/products', 'index');
    Route::post('/products', 'store');
    Route::get('/products/{product}', 'show');
});

步骤 5:路由模型绑定

创建模型和迁移(下一课详细讲 Eloquent,这里先跑通绑定):

php artisan make:model Order -m

控制器方法直接声明类型,框架自动按主键查库,查不到返回 404:

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Order;

class OrderController extends Controller
{
    public function show(Order $order)
    {
        return response()->json($order);
    }
}

默认按 id 绑定。想按订单号绑定,在模型里改写 getRouteKeyName():

// app/Models/Order.php
public function getRouteKeyName(): string
{
    return 'order_no'; // /orders/{order_no}
}

更精细的「软删除数据也允许访问」或「限定当前用户的订单」可以用作用域绑定:

Route::get('/orders/{order}', function (\App\Models\Order $order) {
    abort_unless($order->user_id === auth()->id(), 403);
    return response()->json($order);
})->scopeBindings();

步骤 6:查看与排查

# 列出所有路由:方法、URI、名称、中间件、动作
php artisan route:list

# 只看 orders 相关
php artisan route:list --path=orders

# 按名称过滤
php artisan route:list --name=orders

# 生产环境缓存路由(闭包路由无法缓存,控制器路由可以)
php artisan route:cache
php artisan route:clear

讲清楚要点

  1. 路由定义顺序影响匹配:/orders/report 必须写在 /orders/{order} 之前,否则 report 会被当成 {order} 参数(当参数没有数字约束时)。这是初学者最常踩的坑。
  2. GET 与 HEAD 自动配对:注册 get 路由时框架会同时响应 HEAD(只返回响应头)。POST 路由配合表单使用时必须通过 CSRF 中间件。
  3. 分组属性会合并而非覆盖:多层 prefix 拼接、多层 middleware 叠加,命名可用 ->name('admin.') 给整组加前缀。
  4. 限流直接挂路由:Route::get(...) ->middleware('throttle:60,1') 表示每分钟 60 次,底层通常用 Redis;对应 Spring Cloud Gateway 的 RequestRateLimiter。
  5. 回退路由:Route::fallback(...) 处理没有任何匹配的请求,可自定义 404 JSON。
  6. 表单不能发 PUT/DELETE:HTML 表单只支持 GET/POST,Blade 中用 @method('PUT') 生成 _method 隐藏域,框架据此改写动词。AJAX 直接发 PUT/DELETE 则无需此处理。

你可能会懵的地方

  • 路由写在 web.php 还是 api.php? 返回页面、需要登录态(Cookie + Session + CSRF)的写 web;给 App、小程序、第三方服务的 JSON 接口写 api,它默认带 /api 前缀且无状态。

  • route:cache 报错说无法序列化闭包怎么办? 路由缓存只支持控制器类路由。把闭包路由改成指向控制器方法,再执行缓存。新项目从一开始就坚持「路由只指向控制器」可以避免这个问题。

  • 模型绑定返回 404 而数据明明存在? 检查路由参数名与变量名是否一致({order} 对应 Order $order);若改了 getRouteKeyName(),确认访问 URL 用的是新字段;再检查模型是否被全局作用域(如租户隔离)过滤。

  • 怎么给 API 加版本号最合理? 最常见是 URI 版本 /api/v1/... + 分组;也有用 Header(Accept: application/vnd.shop.v1+json)的方案,但对团队和网关更复杂。没有银弹,选定后全公司统一。

  • throttle 中间件的计数存在哪? 默认走应用的缓存驱动,生产应配 Redis,否则多台机器各自计数、限流失效。Spring 网关同理,限流状态必须集中存储。

错法对照

错法问题正解
/orders/report 写在 /orders/{order} 后面动态路由抢先匹配静态路径在前,动态参数在后
在路由闭包里写 200 行业务逻辑无法缓存路由、无法测试指向控制器方法
控制器方法里手动 Order::findOrFail($id)重复模型绑定做的事直接类型声明 Order $order
所有路由平铺、不做分组前缀和中间件重复prefix + middleware group
前端拼字符串生成接口 URL路径改动全端崩后端用命名路由,前端集中管理 endpoint 常量
给公开接口也挂 auth:sanctum匿名用户 401按资源公开性分组挂中间件

卡点急救

  1. 访问接口 404:先用 php artisan route:list --path=xxx 确认路由真的注册了;api 路由别忘了访问时带 /api 前缀;再检查路由定义顺序与参数约束。
  2. POST 请求 419(CSRF):web 路由的表单缺少 @csrf;若是前后端分离的 API 接口,应写到 api.php(该文件不启用 CSRF 校验),不要硬塞在 web 里。
  3. 改了路由文件没反应:跑过 route:cache 就执行 php artisan route:clear;OPcache 极端情况下可重启 PHP-FPM / Herd 服务确认。

面试常问

  • 路由模型绑定是怎么实现的?查不到数据时发生什么? 路由器在分发前通过容器解析参数:发现类型是 Eloquent 模型时,取路由中同名参数值,隐式调用 whereKey(...)->firstOrFail()(或按 getRouteKeyName 指定字段),找不到抛 ModelNotFoundException,异常处理器渲染为 404。还支持自定义「显式绑定」在 Route::bind() 中接管解析逻辑。

  • 路由中间件、控制器中间件、全局中间件的区别? 全局中间件对每个请求执行(如维护模式、Cookie 加密);路由/分组中间件只作用于匹配的路由(如认证、限流);控制器中间件在构造函数中声明,可限定到某些方法($this->middleware('auth')->only('store'))。

  • 怎么保证团队的 API 风格统一? 统一用 apiResource 表达 RESTful 动作;统一版本前缀分组;统一响应结构(建议在中间件或基类控制器中包装);统一限流与认证中间件;再用 route:list 做代码评审检查项。

自测三问

  1. 不看资料,用 apiResource 一行注册订单的 5 个 API 动作,并说出它们各自的 HTTP 动词和路径。
  2. 为什么 /users/{user}/orders 这种路由要加参数约束?不加会怎样?
  3. 要上线一个新版本接口且老版本保留 6 个月,路由文件你会怎么组织?

小结一句话

路由把 URL、参数、中间件和控制器用链式结构粘合成一份可反向生成、可缓存、可总览的契约表,写好路由等于写好了后端 API 的目录。

延伸

  • Laravel 11 路由文档
  • 路由模型绑定
  • 限流
  • Spring RequestMapping 对照学习
  • 下一课:控制器、中间件与请求响应——进入路由的落点,学习 Request、Response、依赖注入与资源响应。
加载本节练习…
← 上一课目录结构下一课 →控制器