thinkphp笔记-中间件

3064次阅读 145人点赞 作者: WuBin 发布时间: 2024-07-03 08:01:38
扫码到手机查看

定义中间件

中间件的主要用于拦截和过滤HTTP 请求,并进行相应处理;对HTTP提交过来的各种请求的值进行处理和判断。这些请求的功能可以是URL 重定向、权限验证等等;

可以通过命令行模式,在应用目录下生成一个中间件文件和文件夹;

// cmd在项目文件夹中执行
\xuexi\tp6>php think make:middleware Check

成功后,会多出一个app/middleware/Check.php目录和文件,Check.php默认的文件内容如下

<?php
declare (strict_types = 1);

namespace app\middleware;

class Check
{
    /**
     * 处理请求
     *
     * @param \think\Request $request
     * @param \Closure       $next
     * @return Response
     */
    public function handle($request, \Closure $next)
    {
        //
    }
}
有了文件下一步就是要在app/middleware.php中进行注册
<?php
// 全局中间件定义文件
return [
    // 全局请求缓存
    // \think\middleware\CheckRequestCache::class,
    // 多语言加载
    \think\middleware\LoadLangPack::class,
    // Session初始化
    \think\middleware\SessionInit::class,

    // 注册一个自己的中间件
    \app\middleware\Check::class
];

中间件基本结构分析

中间件的入口执行方法必须是:handle()方法,第一参数请求(依赖注入),第二参数是闭包(是为了让回调函数返回response对象);而response对象的作用就是让程序在执行了中间件之后,再继续去执行后面的主体程序。

只要创建并注册了中间件,那么他就会自动、全局强制执行。因此,如果按照文件内容,运行程序就会报错 “中间件方法必须返回Response对象实例”,为了测试拦截后,无法继续执行,可以return response()助手函数测试;
<?php
declare (strict_types = 1);

namespace app\middleware;

class Check
{
    /**
     * 处理请求
     *
     * @param \think\Request $request
     * @param \Closure       $next
     * @return Response
     */
    public function handle($request, \Closure $next)
    {
        // 直接返回空对象可以屏蔽掉无返回的错误
        return response();
    }
}

假如控制器中加载了一个模板,

public function test_middleware()
    {
        echo '主体程序';
        return View::fetch('middleware');
    }

但是会发现这样拦截之后,中间件会中断流程,导致模板不被加载,所以应该采用下面的固定写法:

 public function handle($request, \Closure $next)
    {
        // 固定写法,不阻拦流程的执行
        return $next($request);
    }
$next($request); 这里回调本身会返回response对象

然后加一个拦截的条件判断

public function handle($request, \Closure $next)
 {
        // 处理HTTP请求,中间件代码
        // 如果查询参数中的name属性==index,那么就跳转到首页
        if($request->param('name') == 'index') {
            return redirect('../');
        }

        // 固定写法,不阻拦流程的执行 
        return $next($request);
}

测试访问:http://localhost/xuexi/tp6/public/store/middleware?name=wu 如果请求的name 是wu,那需要继续往下执行控制器中的主体程序才行,不能被拦死;(但是仅仅返回一个空的response对象是没有用的,因为空的response对象里面没有要执行的代码)页面不会跳转,会正常渲染模板;

http://localhost/xuexi/tp6/public/store/middleware?name=index,中间件就会拦截请求,跳转到首页

前/后置中间件

将$next($request)放在方法底部的方式,属于前置中间件;

前置中间件就是请求阶段来进行拦截验证,比如登录判断、跳转、权限等;在执行主体代码之前,先做一系列的验证。

而后置中间件就是等待中间件和主体程序执行完毕之后再进行验证,比如写入日志等等;

下面是一个前置中间件与后置中间件 执行位置

public function handle($request, \Closure $next)
{
        // 前置中间件执行区域
        echo '前置中间件';

        $response = $next($request);

        // 后置中间件执行区域
        echo '后置中间件';

        return $response;
}

如果分开的话

前置中间件

echo '前置中间件';
return $next($request);

后置中间件

$response = $next($request);
echo '后置中间件';
return $response;

结束调度

中间件提供了一个end()方法,可以在中间件执行到最后时执行; 就是无论前置、后置、主体程序、模板加载等待都执行完毕后,就会执行end()钩子。

<?php
declare (strict_types = 1);

namespace app\middleware;

use think\Response;

class Check
{
    /**
     * 处理请求
     *
     * @param \think\Request $request
     * @param \Closure       $next
     * @return Response
     */
    public function handle($request, \Closure $next)
    {
        // 直接返回空对象可以屏蔽掉无返回的错误
        // return response();

        // 处理HTTP请求
        // 如果查询参数中的name属性==index,那么就跳转到首页
        // if($request->param('name') == 'index') {
        //     return redirect('../');
        // }

        // 前置中间件执行区域
        echo '前置中间件';

        $response = $next($request);

        // 后置中间件执行区域
        echo '后置中间件';

        // 固定写法,不阻拦流程的执行 
        return $response;
    }

    public function end(Response $response)
    {
        echo '结束';
    }
}

end钩子也是自动执行,属于收尾工作了。

路由中间件

创建一个给路由使用的中间件,判断路由的ID 值实现相应的验证;

首先在项目目录下启动服务

php think run

然后访问:http://127.0.0.1:8000/

这时候需要将app/middleware.php中,上面新增的全局中间件先注释掉

app/middleware.php这个文件是注册全局中间件的!
// 全局中间件定义文件
return [
    // 全局请求缓存
    // \think\middleware\CheckRequestCache::class,
    // 多语言加载
    \think\middleware\LoadLangPack::class,
    // Session初始化
    \think\middleware\SessionInit::class,

    // 注释掉这个
    // \app\middleware\Check::class
];

然后在controller/Address.php类中 新写一个控制器

class Address {
   public function read($id)
    {
        return 'id' . $id;   
    }
}

然后新建并在app/middleware/Auth.php中

<?php
declare (strict_types = 1);

namespace app\middleware;

use think\Response;

class Auth
{
    /**
     * 处理请求
     *
     * @param \think\Request $request
     * @param \Closure       $next
     * @return Response
     */
    public function handle($request, \Closure $next)
    {

        if($request->param('id') == 10) {
            echo '管理员';
        }

        $response = $next($request);

        // 固定写法,不阻拦流程的执行 
        return $response;
    }
}

判断当传入的参数Id=10的时候,打印一句话。

路由方法提供了一个middleware()方法,让指定的路由采用指定的中间件;

最后在根目录下的route/app.php中,注册一个路由,并指向AUth

Route::rule('ar/:id', 'Address/read')
    ->middleware(\app\middleware\Auth::class)
;

http://127.0.0.1:8000/ar/10 会输出:管理员id10

当然在路由中使用中间件也支持引用模式:

use app\middleware\Check;
use app\middleware\Auth;
// 如果是引用的话 就不需要前面加\app之类的了
Route::rule('ar/:id', 'Address/read')
    ->middleware(Auth::class)
;

也支持添加多个中间件,使用数组传入

// 也支持添加多个中间件
Route::rule('ar/:id', 'Address/read')
    ->middleware([ Auth::class, Check::class ])
;

也可以在根目录下的config/middleware.php 配置文件加中,配置别名支持(config与app目录同级);

<?php
// 中间件配置
return [
    // 别名或分组 默认alias这里是空数组
    'alias'    => [
        'Auth' => \app\middleware\Auth::class,
        'Check' => \app\middleware\Check::class
    ],
    // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
    'priority' => [],
];

设置好之后,在route/app.php

Route::rule('ar/:id', 'Address/read')
    ->middleware([ 'Auth', 'Check' ])
;

可以给中间件传递额外参数,通过中间件入口方法的第三个参数接收;

在route/app.php中,给->middleware传入第二个参数

Route::rule('ar/:id', 'Address/read')
    ->middleware([ 'Auth', 'Check' ], 'ok')
;

那么该如何接收这个参数?在app/middleware/Auth.php中

public function handle($request, \Closure $next, $param)
    {

        if($request->param('id') == 10) {
            echo '管理员' . $param;
        }

        $response = $next($request);

        // 固定写法,不阻拦流程的执行 
        return $response;
}

这里hanlde可以接收第三个参数,这个参数就是接收从路由中->middleware方法传递来的第二个参数的数据的。

中间件也支持分组路由,闭包路由等;

// 分组路由
Route::group('ar', function () {
    Route::rule(':id', 'Address/read')
})->middleware(Auth::class);

闭包路由,因为通常使用的时候,不值得为了简单的一个功能需求,就单独再在app/middleware/中新建单独的一个类,那么这时候就可以考虑使用闭包中间件,直接将中间件的相关操作写在闭包中:

Route::rule('ar/:id', 'Address/read')
    ->middleware(function($request, \Closure $next) {
        if($request->param('id') == 10) {
            echo '管理员' ;
        }

        // 固定写法,不阻拦流程的执行 
        return $next($request);
    })
;
注意,闭包中function的传参,与单独类中hanle中的参数一样,都必须是$request, \Closure$next,这是固定写法!

控制器中间件

比如我这个中间件不是在全局做,而只是在某个控制器中使用。

可以让中间件在控制器里注册,让这个控制器执行的时候执行中间件;

class Address
{
    // 只需要在开头加这么一段
    protected $middleware = ['Check'];

    public function read($id)
    {
        return 'id' . $id;   
    }
}
注意,上面这个写法依赖在config/middleware.php中配置依赖alias=》[..],(就像上文那样配置),如果不提前配置依赖
class Address
{   
    // 如果配置了依赖
    // protected $middleware = ['Check'];
    // 如果不配置依赖就使用下面这种方式
    protected $middleware = [ \app\middleware\Check::class ];

    public function read($id)
    {
        return 'id' . $id;   
    }
}

如此配置后,这个控制器中无论执行/address/read/id/10、或者/address/index 等等方法,都会执行这个引入的中间件。会针对这个控制器的全局执行。

默认情况下,控制器中间件对所有操作方法有效,支持做限制;

如果我只要对这个address控制器中某些方法有效

protected $middleware = [
   'Auth' => ['only' =>['index']], // 只对哪些方法有效
   'Check' => ['except' =>['read']],  // 对哪些方法无效,排除哪些方法
];
以上也是针对已经设置的依赖的情况,如果没设置依赖,那么还是使用\app引入或者在文件头use..即可

中间件给控制器传递参数,通过Request 对象实现;

我在app/middleware/Check.php文件中,向控制器address中传参

Check.php

public function handle($request, \Closure $next) {
        
        // 通过中间件向控制器传参
        $request->name = 'wubin';

        // 固定写法,不阻拦流程的执行 
        return $next($request);
}

在控制器Address.php中,接收参数

namespace app\controller;

use app\Request; // 通过request传递接收参数

class Address
{   
    public function read(Request $request, $id)
    {
        echo $request->name;
        return 'id' . $id;   
    }
}
点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:thinkphp
推荐阅读
  • uniapp实现被浏览器唤起的功能

    当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

    9094次阅读 588人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • Vue

    盘点Vue2和Vue3的10种组件通信方式

    Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

    3843次阅读 286人点赞 发布时间: 2022-08-19 09:40:16 立即查看
  • JS

    几个高级前端常用的API

    推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

    14071次阅读 914人点赞 发布时间: 2021-11-11 09:39:54 立即查看
  • PHP

    【正则】一些常用的正则表达式总结

    在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

    12908次阅读 442人点赞 发布时间: 2021-10-09 15:58:58 立即查看
  • 【中文】免费可商用字体下载与考证

    65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

    11469次阅读 920人点赞 发布时间: 2021-07-05 15:28:45 立即查看
  • Vue

    Vue3开发一个v-loading的自定义指令

    在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

    15588次阅读 1244人点赞 发布时间: 2021-07-02 15:58:35 立即查看
  • JS

    关于手机上滚动穿透问题的解决

    当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

    14795次阅读 1205人点赞 发布时间: 2021-05-31 09:25:50 立即查看
  • Vue

    Vue+html2canvas截图空白的问题

    在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

    28950次阅读 2273人点赞 发布时间: 2021-03-02 09:04:51 立即查看
  • Vue

    vue-router4过度动画无效解决方案

    在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

    24982次阅读 1925人点赞 发布时间: 2021-02-23 13:37:20 立即查看
交流 收藏 目录