刚用TP8的时候,我对“中间件”三个字有点发怵,总觉得是什么高大上的东西。直到有一天要做一个后台的登录校验,我才发现中间件就像一扇门,请求进来先过门,你的登录验证逻辑写在门里,没通过就直接拦在外面。不用在每个控制器里重复写判断,省事太多了。
中间件在TP8里长什么样
简单说,中间件就是一个类,里面有一个 handle 方法。系统在调用控制器之前会先调用这个 handle 方法,你拿到的参数是请求对象 $request 和一个闭包 $next。如果你允许请求继续往下走,就调用 $next($request),否则就返回一个响应(比如重定向或错误提示)。
<?php
namespace appmiddleware;
use Closure;
use thinkRequest;
use thinkResponse;
class AuthCheck
{
public function handle(Request $request, Closure $next)
{
// 这里写你的校验逻辑
if (session('admin_id')) {
return $next($request);
} else {
return redirect('/admin/login')->with('error', '请先登录');
}
}
}
创建中间件
TP8里你可以通过命令行快速生成中间件类。在项目根目录执行:
php think make:middleware AuthCheck
它会自动在 app/middleware 目录下生成一个 AuthCheck.php 文件。如果你更喜欢手动创建,那就在该目录下新建对应的类文件,一样能用。
注册中间件
中间件可以在三个地方使用:全局、路由、控制器。先说最简单的全局注册:在 app/middleware.php 里把中间件加到数组里。
// app/middleware.php
return [
appmiddlewareAuthCheck::class
];
这样一来,所有请求都会经过 AuthCheck,不管你是访问后台还是首页。不过一般我不会把登录校验全部注册成全局,因为有些页面(如登录页本身)并不需要登录。
更常用的是给路由分组注册。假设你的后台所有路由都在 route/admin.php 中定义,你可以这样写:
// route/admin.php
use thinkfacadeRoute;
Route::group('admin', function () {
Route::get('dashboard', 'admin.dashboard/index');
Route::get('order/list', 'admin.order/list');
Route::get('user/delete', 'admin.user/delete');
})->middleware(appmiddlewareAuthCheck::class);
使用->middleware()方法,就把这个组里的所有路由都挂上了登录校验。
中间件的执行顺序
如果你注册了多个中间件,它们会按照注册顺序依次执行,就像洋葱一样。全局中间件先跑,然后是路由中间件,最后是控制器中间件。每个中间件都可以决定是否放行到下一层。我一般把登录校验放在路由中间件,因为它只针对后台的URL。
实战:完成一个后台登录过滤器
下面完整的写一套登录验证,包括中间件和登录控制器。假设后台所有的控制器都放在 appcontrolleradmin 命名空间下。
第一步:创建登录控制器(省略M层)
<?php
namespace appcontrolleradmin;
use thinkController;
use thinkfacadeDb;
use thinkfacadeSession;
class Auth extends Controller
{
// 显示登录页
public function login()
{
return view('login');
}
// 处理登录
public function doLogin()
{
$username = input('post.username');
$password = input('post.password');
$user = Db::name('admin')->where('username', $username)->find();
if ($user && password_verify($password, $user['password'])) {
Session::set('admin_id', $user['id']);
Session::set('admin_name', $user['username']);
return redirect('/admin/dashboard');
}
return redirect('/admin/login')->with('error', '用户名或密码错误');
}
// 退出
public function logout()
{
Session::delete('admin_id');
Session::delete('admin_name');
return redirect('/admin/login');
}
}
第二步:创建中间件 AuthCheck
<?php
namespace appmiddleware;
use Closure;
use thinkRequest;
use thinkResponse;
class AuthCheck
{
public function handle(Request $request, Closure $next)
{
// 如果已登录就直接放行
if (session('admin_id')) {
return $next($request);
}
// 没登录时,判断是不是AJAX请求,返回JSON或重定向
if ($request->isAjax()) {
return json(['code' => -1, 'msg' => '请先登录'])->code(401);
}
// 普通请求重定向到登录页
return redirect('/admin/login');
}
}
第三步:注册路由
// route/admin.php
use thinkfacadeRoute;
// 无需登录的请求
Route::get('admin/login', 'admin.Auth/login');
Route::post('admin/logindo', 'admin.Auth/doLogin');
Route::get('admin/logout', 'admin.Auth/logout');
// 需要登录的分组
Route::group('admin', function () {
Route::get('dashboard', 'admin.Dashboard/index');
Route::get('user/list', 'admin.User/list');
Route::post('user/delete', 'admin.User/delete');
})->middleware(appmiddlewareAuthCheck::class);
这里注意,登录页面和登录处理的路由不要放在需要验证的分组里,否则就永远无法访问登录页了。
第四步:测试效果
当你访问 http://你的域名/admin/dashboard 而没有登录时,中间件会把你重定向到 /admin/login。一旦登录成功,后面再访问后台页面就会正常通过。
让中间件更加灵活:支持参数和排除项
有时你希望后台的“用户中心”页面也允许未登录的用户查看,但其他页面不行。这时候你可以给中间件传参数,或者干脆在中间件里判断当前请求的路由地址。
我采取一个相对简单的方法:在中间件里获取当前访问的控制器名,如果是 Auth 或者 Public 就放行。但这样不够优雅。更推荐的方式是写一个“白名单”配置在路由上。比如:
Route::group('admin', function () {
// 公开路由
Route::get('public/help', 'admin.Public/help');
// 受保护路由
Route::get('dashboard', 'admin.Dashboard/index');
})->middleware(appmiddlewareAuthCheck::class);
但你可能会发现,分组中间件会作用到组内的所有路由,包括公开的 public/help。要是想单独排除,那就不能用分组了。而是单独在单个路由上挂中间件:
Route::get('admin/public/help', 'admin.Public/help');
Route::get('admin/dashboard', 'admin.Dashboard/index')->middleware(appmiddlewareAuthCheck::class);
这样也能实现。
中间件里的前置和后置操作
中间件不止能“过滤”请求,还能在请求处理完后再做一些收尾工作。你可以在 $next($request) 之后写逻辑。比如记录访问日志、追加响应头等。
class LogMiddleware
{
public function handle(Request $request, Closure $next)
{
// 请求之前
$response = $next($request);
// 请求之后
$response->header('X-Powered-By', 'ThinkPHP');
return $response;
}
}
在我自己的后台项目里,我用中间件改写过HTTP响应头,用来防止安全隐患,效果很好。
中间件和事件区别
可能你会问:事件也能在操作后记录日志,那和中间件有什么区别?中间件是请求处理流程里的一环,适合做请求级的操作(如权限、跨域、过滤)。事件则类似于观察者模式,更适合解耦业务逻辑,比如订单创建后发通知,和请求流程无关。一般来说,能在中间件做的事就不要丢到事件里,因为中间件能拿到请求和响应对象。
一个容易犯的错误:sesssion在中间件里无法启动
我在一开始测试时,发现中间件里调用 session('admin_id') 拿不到值,后来才明白是因为中间件执行时还未开启session。TP默认在全局中间件中已经启动了session,但如果你在路由中间件或控制器中间件中还没初始化session,可能会出问题。解决办法是在 app/middleware.php 中确保session已经注册。我的做法是使用thinkmiddlewareSessionInit::class作为最早执行的全局中间件:
// app/middleware.php
return [
thinkmiddlewareSessionInit::class,
appmiddlewareLogMiddleware::class,
// 其他全局中间件
];
这样你在后面的中间件中就能安全使用session了。
更复杂的场景:API接口的多语言和鉴权
中间件还经常用于API接口,比如验证签名、频率限制、检测Token。我最近在做一个开放接口,就在中间件里解析JWT,把用户ID存到请求对象中,后续控制器就能直接用 $request->userId 获取当前登录用户。这个方法特别干净。
不过这块就是另一个长话题了。今天先记录一下中间件的基本玩法和后台登录的例子,希望能帮你少踩些坑。你要是也写过有用的中间件,欢迎交流。

