监控系统实践第13天:5天上手Laravel8.5之中间件篇(2/5)

Posted 家铭didphp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了监控系统实践第13天:5天上手Laravel8.5之中间件篇(2/5)相关的知识,希望对你有一定的参考价值。

前言

很多语言的框架中都有中间件,只是名称、叫法、存在形式不同罢了。

中间件真的非常好用,用得好省时又省力。

昨天介绍了Laravel8.5的路由,今天来介绍一下它的中间件,重点内容会放在注册中间件上面。


Laravel基础功能:中间件(Middleware)

中间件是什么?

中间件会在路由被调用之前之后执行,它的作用,有点点像过滤器、拦截器之类的吧。

你可以在里面埋入过滤参数的代码,同时也可以埋入并执行一些自己自定义的逻辑代码。


如何运用?

中间件在实际运用中,可以有什么作用呢?比如可以进行一些HTTP请求的参数过滤,检查用户是否登录、用户的输入是否非法等。

可以使用中间件的地方有很多,很多地方可以用到一个中间件,也可以用到多个不同的中间件

在使用中间件之前,要确定一下是什么场景,什么情况下使用它,是否有必要使用到它。


实际运用

中间件相对路由更容易理解,更容易上手。

下面我们通过5个步骤,结合实例,来编写并使用一个真实可用的中间件


1. 确定中间件场景

现在我用一个实际的例子,来介绍一下Laravel 中间件的写法与用法。

假设现在有一个场景如下:

  1. 现有的站点提供了很多API接口供外部调用;
  2. 每次访客都会传递字符串与数字给对应的接口;
  3. 有三个指定的参数:iduidorder_id,必须为数字,否则会对网站存在一定的安全与性能隐患。

2. 分析场景情况

从刚才提供的场景可以得知,不论有多少接口,有多少数据要传递,最终要防范的,是参数:iduidorder_id对应的值。

3. 编写中间件

明确了要处理防范的内容,编写起来就很简单了。从上面得知,我们只需要处理三个参数:iduidorder_id即可。

3.1 创建中间件

所有的中间件文件,默认放在文件夹/app/Http/Middleware 下,这里我也建议不要到处放,以免造成各个文件夹的职责混乱

中间件可以通过:命令手工的方式创建。

想要创建中间件,除了原始的手工方式外,还有一种方式就是通过命令来创建,官方文档是使用命令创建,这里推荐使用命令

下面开始创建需要过滤那三个参数的中间件,我命名为:FilterParams,到Laravel的根目录下执行:

php artisan make:middleware FilterParams

执行之后,会出现以中间件名称命令的PHP文件FilterParams.php
在这里插入图片描述
打开刚才创建的中间件:/app/Http/Middleware/FilterParams.php,简单的看一下代码:
在这里插入图片描述
上图中可以看到,有一个公共方法:handle(),这个方法可以说是中间件的核心方法了,如果启用了中间件,框架就会调用被启用中间件handle()方法。

如果要增加自己的业务逻辑,要在这个方法的return之前添加处理。

3.2 注册中间件(重点章节)

注册相当于是启用,启用后,中间件就开始生效了。

刚创建,不写业务逻辑就启用?

是的,还没开始写代码, 不启用,怎么调试?


现在我们来启用刚才创建的中间件FilterParams

直接打开/app/Http/Kernel.php,可以看到里面定义了3protected受保护的变量,分别为:$middleware$middlewareGroups$routeMiddleware,且都是数组:
在这里插入图片描述
/app/Http/Kernel.php是什么文件,起什么作用?
是存放需要调用的中间件信息的,需要调用哪些中间件,全部都要在这个文件里面注册登记。

文件里面的这3个受保护的变量都是些什么?
3个受保护变量,分别有不同的含义:

  1. $middleware全局中间件,只要加入进来的中间件,不需要到别的地方做任何配置,每次请求都会被执行一次;
  2. $middlewareGroups中间件组,哪个地方需要就在哪个地方用;
  3. $routeMiddleware路由中间件,有点像中间件组,哪个路由需要,就在哪个路由上面用。

中间件组路由中间件是不是有点懵?到底我要在哪个变量里面注册开启我那个FilterParams的中间件?
说实在的,我要是不懂的话,现在就懵了。不过不用着急,现在来结合例子一一介绍。


3.2.1 注册全局中间件(核心小节)

假设现在我要把中间件FiterParams设置为全局中间件,使用它来处理三个参数:iduidorder_id的值。

打开/app/Http/Kernel.php,里面定义了3protected受保护的变量,其它的不管,只管负责全局中间件的变量:$middleware
在这里插入图片描述
避免出现其它问题,原来的内容我们不动,只在最下面增加`FilterParams:

\\App\\Http\\Middleware\\FilterParams::class,

在这里插入图片描述
增加后,原先的整个$middleware就变为:

protected $middleware = [
        // \\App\\Http\\Middleware\\TrustHosts::class,
        \\App\\Http\\Middleware\\TrustProxies::class,
        \\Fruitcake\\Cors\\HandleCors::class,
        \\App\\Http\\Middleware\\PreventRequestsDuringMaintenance::class,
        \\Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize::class,
        \\App\\Http\\Middleware\\TrimStrings::class,
        \\Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull::class,
        \\App\\Http\\Middleware\\FilterParams::class,
    ];

这个时候可以直接写中间件的代码来看效果了。


打开之前创建的中间件:/app/Http/Middleware/FilterParams.php,会看到handle()方法会传递一个Request $request进来,现在需要进行处理的,就是它了。
在这里插入图片描述
这里简单的说一下RequestRequestLaravel自有的获取用户输入的类,可以获取浏览器传递的GETPOSTJSON等数据,后面我会抽一天介绍。


现在知道要处理的参数,我们接收参数来做判断即可,只是不知道参数是通过什么方式传递。

从表面上看,可以排除很少人用的JSON方式,剩下GETPOST,现在只管这两个方式。


因此,如果只考虑GET方式传递那三个参数过来,我们可以这样编写中间件的代码:

  1. 如果三个指定的参数,没有传值,就给一个默认值:0,代表没有传值;
$id = $request->query('id', 0);
$uid = $request->query('uid', 0);
$order_id = $request->query('order_id', 0);
  1. 如果三个参数都传值了,就不会为0,被强制转成数字后,如果跟以前不相等,就为不合法的值;
//如果三个参数都传值了,就不会为`0`,被强制转成数字后,如果跟以前不相等,就为不合法的值
if ($id != '0' && $id != intval($id)) {
    echo '参数 id 的值不合法';
    exit;
}
 if ($uid != '0' && $uid != intval($uid)) {
    echo '参数 uid 的值不合法';
    exit;
 }
 if ($order_id != '0' && $order_id != intval($order_id)) {
    echo '参数 order_id 的值不合法';
    exit;
 }
  1. 整个流程的代码一定要写在return $next($request)的那行之前,且不能动那一行代码,现在只要发现三个参数中有一个不是数字,整个请求就中断。
    在这里插入图片描述

如果是POST呢?这里不占篇幅浪费时间了,逻辑跟上面一样,只是把$request->query更换成$request->post

那在不确定对方是GET还是POST的情况下,有没有更方便的方法?
有,使用$request->input()方法,将存在的$request->query或者$request->post,替换为$request->input,它既可以获取GET传参的值,也可以获取到POST传参的值。

全局中间件是全局的,因此启用后,根本不需要配置路由来测试路径,直接在浏览器里执行当前站点的URL访问即可查看效果。
在这里插入图片描述


3.2.2 注册中间件组

中间件组全局中间件一样,都是打开/app/Http/Kernel.php修改变量,只是它的变量为里面定义了3protected受保护的变量,其它的不管,只管负责全局中间件的变量:$middlewareGroups
在这里插入图片描述
需要注意的是,中间件组默认存在两个下标:webapi

下标名称,就是下次调用的时候需要填写的名称,需要增加的中间件就填在其中一个下标之中,也可以自己增加下标,每个下标都对应一个数组。

可以继续使用刚才编写好的FilterParams,我现在把它从全局中间件的调用中去除,运用到中间件组中,放到下标web的末尾:
在这里插入图片描述
这个地方的调用,就需要自己去路由中定义了,路由中多调用一个方法middleware来指定中间件组,我这里选择web

Route::get('/abc', function (Request $request) {
    return "Hello World " . $request->input('id');exit;
})->middleware('web');

这个地方正常情况下,只要传递的id是数字,就会正常输出:
在这里插入图片描述
如果id为非数字,就会触发中间件里的中断操作:
在这里插入图片描述
如果不太记得路由了,可以看这篇:监控系统实践第12天:5天上手Laravel8.5之路由篇(1/5)


3.2.3 注册路由中间件

路由中间件中间件组类似,只是下标没有那么深。

同样是找到/app/Http/Kernel.php,里面的路由中间件组变量为:$routeMiddleware
在这里插入图片描述
路由中间件是一个下标对应一个中间件中间件组是一个下标对应多个中间件

刚才的FilterParams再次利用,注意中间件组路由中间件的不要存在重复的下标,可以为它定义一个下标:filter_params

'filter_params' => \\App\\Http\\Middleware\\FilterParams::class,

默认完整的路由中间件代码为:

protected $routeMiddleware = [
    'auth' => \\App\\Http\\Middleware\\Authenticate::class,
    'auth.basic' => \\Illuminate\\Auth\\Middleware\\AuthenticateWithBasicAuth::class,
    'cache.headers' => \\Illuminate\\Http\\Middleware\\SetCacheHeaders::class,
    'can' => \\Illuminate\\Auth\\Middleware\\Authorize::class,
    'guest' => \\App\\Http\\Middleware\\RedirectIfAuthenticated::class,
    'password.confirm' => \\Illuminate\\Auth\\Middleware\\RequirePassword::class,
    'signed' => \\Illuminate\\Routing\\Middleware\\ValidateSignature::class,
    'throttle' => \\Illuminate\\Routing\\Middleware\\ThrottleRequests::class,
    'verified' => \\Illuminate\\Auth\\Middleware\\EnsureEmailIsVerified::class,
    'filter_params' => \\App\\Http\\Middleware\\FilterParams::class,
];

效果如下:在这里插入图片描述
调用也是需要自己去路由中定义,和中间件组的方法一样,我这里选择就成了filter_params

Route::get('/abc', function (Request $request) {
    return "Hello World " . $request->input('id');exit;
})->middleware('filter_params');

路由文件中效果如图:
在这里插入图片描述
中间件组一样,正常情况下,只要传递的id是数字,就会正常输出:
在这里插入图片描述
如果id为非数字:
在这里插入图片描述


3.3 后置中间件

一开始说过,中间件会在路由被调用之前之后执行,即分为:前置中间件后置中间件

前面全局中间件中间件组路由中间件的写法,全部属于前置中间件前置中间件主要处理请求前的参数,后置中间件主要处理请求后的结果。

现在来介绍一下后置中间件的写法。


继续使用之前定义的中间件FiterParams,继续来处理它的handle()方法。

中间件是属于前置还是后置,最好的判断方法就是,看handle()方法的最后一行return $next($request);,看是否有代码存在于$next($request)之后,就是后置中间件,反之为前置中间件


简单的回顾一下前置中间件的写法:

/**
 * 逻辑代码开始
 **/
if ($request->post('id') > 0) {
    exit;
}
/**
 * 逻辑代码结束
 **/
return $next($request);

简单的后置中间件代码是这么写,代码写在 $next() 之后,最后 return $next() 的内容:

$response = $next($request);
/**
 * 逻辑代码开始
 **/
\\Illuminate\\Support\\Facades\\Storage::disk('public')->append("abc.log", 'request finished');
 /**
  * 逻辑代码结束
  **/
return $response;

请求结束后,在文件abc.log中写入执行的内容,一般日志的做法。
在这里插入图片描述


3.4 中间件的其它调用方法

中间件组路由中间件,除了传递传递下标之外,还有其它的调用方法。

以下,列举几个常用的中间件组路由中间件的调用方法:

  1. 传递下标;
Route::get('admin/profile', function () {
})->middleware('check_age');
  1. 传递类名;
Route::get('admin/profile', function () {
})->middleware(\\App\\Http\\Middleware\\CheckAge::class);
  1. 传递多个中间件,用逗号,隔开,如下为类名与下标;
Route::get('admin/profile', function () {
})->middleware(\\App\\Http\\Middleware\\CheckAge::class, 'auth');
  1. 路由分组使用中间件;
# 方法 1
Route::group(['middleware' => ['check_age']], function () {});
# 方法 2
Route::middleware(['check_age', 'auth'])->group(function () {});
  1. 路由分组中,某个路由,排除其中一个中间件withoutMiddleware()
Route::middleware([CheckAge::class])->group(function () {
    Route::get('/', function () {
    });
    Route::get('admin/profile', function () {
    })->withoutMiddleware([CheckAge::class]);
});

3.5 中间件的排序

中间件排序是框架本身默认的,定义在/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php中。
在这里插入图片描述
如果想自己的中间件优先执行,必须修改这个文件中的变量$middlewarePriority来达到覆盖原有顺序的目的:

protected $middlewarePriority = [
        \\Illuminate\\Cookie\\Middleware\\EncryptCookies::class,
        \\Illuminate\\Session\\Middleware\\StartSession::class,
        \\Illuminate\\View\\Middleware\\ShareErrorsFromSession::class,
        \\Illuminate\\Contracts\\Auth\\Middleware\\AuthenticatesRequests::class,
        \\Illuminate\\Routing\\Middleware\\ThrottleRequests::class,
        \\Illuminate\\Session\\Middleware\\AuthenticateSession::class,
        \\Illuminate\\Routing\\Middleware\\SubstituteBindings::class,
        \\Illuminate\\Auth\\Middleware\\Authorize::class,
];

这里建议不要在有原始顺序的文件上直接修改顺序,应在文件:/app/Http/Kernel.php上重新进行覆盖定义。

需要注意的是,排序只会影响中间件组路由中间件的执行顺序,不会影响到全局中间件


3.6 中间件参数

中间件也是可以接收额外的参数的,比如外部可以传递用户的角色信息。

再次就FilterParams说事,这个文件刚创建好的时候handle()方法是默认存在两个参数的:

public function handle(Request $request, Closure $next)

实际我们可以定义更多变量,传递更多数据进去:
在这里插入图片描述
上图中,我多定义了一个$role代表用户的权限,一个$gender代表用户性别。

假如现在有个中间件下标为role,对应的中间件类名为CheckRole,需要传递指定的值admin给它,调用方法为:

Route::get('admin/profile', function () {})->middleware('role:admin');

如果我要同时传递$gendermale进去,,逗号隔开即可:

Route::get('admin/profile', function () {})->middleware('role:admin,male');

最后就是$role等于admin$gender等于male


3.7 Terminable 中间件

前置中间件是在请求执行之前执行,后置中间件是在请求执行之后执行,而Terminable 中间件则跟它们大不相同,主要是指中间件中的一个方法terminate()

这个方法可以获得请求参数请求返回的内容,它在请求执行完毕之后,在整个HTTP响应结束后执行。

每个中间件都可以定义terminate()方法,比如整个HTTP响应完毕后,需要刷新某些数据的操作,需要在 中间件类的handle()方法后面,就定义terminate()方法:

public function terminate($request, $response) {
    //刷新数据
}

在这里插入图片描述


值得注意的是,编写完terminate()方法的代码后,需要到/app/Providers/AppServiceProvider.php文件中去注册对应的中间件类。
在这里插入图片描述
比如我现在创建了一个中间件,名称为:FitlerParams,找到/app/Providers/AppServiceProvider.phpregister()方法,添加中间件FitlerParams的注册代码:

public function register()
{
    # 这里添加 FitlerParams的注册代码
    $this->app->singleton(\\App\\Http\\Middleware\\FitlerParams::class);
}

注册完毕后,Terminable 中间件开始生效,它具体有哪些用途,产生什么效果,这里就不多啰嗦了。

最后的补充

我的观点是,中间件很好用,但是绝对不是必须出现在项目中的,如果不太理解,先跳过这个章节,慢慢的根据自己学习的进度,熟悉了整个框架后,再回过来看,会更容易理解。

以上的阐述只对有基础的朋友有作用,如果对以上的知识点有质疑或者不懂的可以在下方留言。

以上是关于监控系统实践第13天:5天上手Laravel8.5之中间件篇(2/5)的主要内容,如果未能解决你的问题,请参考以下文章

监控系统实践第15天:5天上手Laravel8.5之HTTP请求篇(4/5)

监控系统实践第15天:5天上手Laravel8.5之HTTP请求篇(4/5)

监控系统实践第14天:5天上手Laravel8.5之控制器篇(3/5)

监控系统实践第10天:用宝塔部署Laravel8.5框架的站点

监控系统实践第9天:使用Composer下载Laravel8.5

监控系统实践第11天:配置Laravel8.5与开关网站方法