ThinkPHP8中间件实战:一个请求日志+接口鉴权的简单实现

2026-08-27 0 805

上个月把公司一个老项目从 ThinkPHP6 升到了 ThinkPHP8,顺便把之前一直想做的“接口日志”和“统一鉴权”给收拾了。以前这两个功能,我在每个控制器里复制粘贴,改一个地方就要全局搜一遍,特别痛苦。后来发现 TP8 的中间件机制比 TP6 更灵活,只需要在中间件里写一遍,就能管住所有请求。

这篇文章就记录一下我怎么在 TP8 里用中间件同时做请求日志和登录校验,完整代码都在下面。这不是官方文档的复述,是我实际跑通后摘出来的,直接复制到项目里改改就用。

一个老项目的混乱现场

以前我的登录验证是这么干的:在每个控制器方法第一行写

$user = $this->checkLogin();
if (!$user) return json(['code'=>401, 'msg'=>'请先登录']);

这样写问题很多:忘了复制这段代码的接口就裸奔;前端调用接口返回格式也不统一;日志记录更是靠人肉。后来我决定用中间件统一处理,控制器里只留业务逻辑。

创建中间件

TP8 里可以用命令行生成中间件类:

php think make:middleware ApiCheck

这个命令会在 appmiddlewareApiCheck.php 生成一个空类。我给它写了两个核心逻辑:请求日志 + 鉴权。

先放上完整代码:

<?php
declare(strict_types=1);

namespace appmiddleware;

use Closure;
use thinkfacadeLog;
use thinkfacadeCache;
use thinkRequest;

class ApiCheck
{
    public function handle(Request $request, Closure $next)
    {
        // 1. 记录请求日志
        $this->writeLog($request);

        // 2. 验证登录(简单示例用 token,从 header 里取)
        $token = $request->header('token', '');
        $user  = Cache::get('user_' . $token);
        if (!$user) {
            return json([
                'code' => 401,
                'msg'  => '登录已过期,请重新登录',
            ])->code(200);
        }

        // 3. 把用户信息绑定到请求对象上,方便后续控制器获取
        $request->user = $user;

        return $next($request);
    }

    /**
     * 写日志的简单实现,实际项目可以改成异步或者用消息队列
     */
    private function writeLog(Request $request)
    {
        $path = $request->pathinfo();
        $method = $request->method();
        $ip = $request->ip();
        $params = $request->param();

        // 密码字段脱敏,避免明文记录
        if (isset($params['password'])) {
            $params['password'] = '******';
        }

        $logData = [
            'path' => $path,
            'method' => $method,
            'ip' => $ip,
            'params' => json_encode($params, JSON_UNESCAPED_UNICODE),
            'time' => date('Y-m-d H:i:s'),
        ];

        Log::write('API请求: ' . json_encode($logData, JSON_UNESCAPED_UNICODE), 'api');
    }
}

注意这里 $request->user 并不是自带的属性,PHP 动态属性在 TP8 里可以直接用,因为 Request 类支持动态属性。这样后续在控制器里就能用 $request->user 拿到用户信息,不用再查一遍缓存。

注册中间件,并排除不需要登录的接口

TP8 的中间件可以在路由/控制器/全局等不同层级注册。我这里用全局中间件最省事,但登录校验不能搞一刀切,像登录接口、注册接口、验证码接口不能拦。我把中间件注册到 app/middleware.php 里,并用一个循环动态排除。

// app/middleware.php
<?php
// 全局中间件定义
return [
    // 不用登录的接口前缀
    appmiddlewareApiCheck::class,
];

这样所有请求都会经过 ApiCheck。现在我需要在中间件里判断当前路径是否在免登录名单里。怎么搞?我选择在中间件里加一个白名单数组:

private $whitelist = [
    'login/login',
    'login/captcha',
    'public/index',
];

然后在 handle 方法最前面加一段:

$path = $request->pathinfo();
if (in_array($path, $this->whitelist)) {
    // 不需要登录,直接放行
    return $next($request);
}

这样登录接口就不会被拦截了。注意 pathinfo() 返回的是类似 login/login,不包含入口文件,如果你有路由后缀需要自己调整。

控制器里怎么用

中间件放行后的请求,控制器只需要直接从 Request 对象里取用户信息就行,不用再调校验方法。比如用户中心的接口:

<?php
namespace appcontroller;

use thinkRequest;

class User
{
    public function profile(Request $request)
    {
        // 中间件已经给 $request->user 赋了值,直接取
        $user = $request->user;

        return json([
            'code' => 200,
            'data' => [
                'id' => $user['id'],
                'name' => $user['name'],
            ]
        ]);
    }
}

看,控制器不再关心是否登录,只关心自己的业务。如果你需要知道当前用户是谁,用 $request->user 就有。多个接口都是这样,代码统一了。

通过路由给中间件分组更精细

全局中间件的好处是漏不掉,坏处是想精细化控制时还要写白名单。后来我参考 TP8 官方文档,把中间件按路由分组来注册,这样更精确。比如在 route/app.php 里:

Route::group(function () {
    Route::post('user/profile', 'User/profile');
    Route::post('order/list', 'Order/list');
})->middleware(appmiddlewareApiCheck::class);

Route::post('login/login', 'Login/login'); // 不经过中间件

这种方式更清爽,但我个人更偏爱全局中间件+白名单,因为有时候后端同学会漏写 middleware,导致某些接口被裸奔。全局中间件更安全,只要在白名单外就必须登录,很难因为忘记注册而出错。

实际踩坑:写日志把接口拖慢了

最开始我是同步写日志到日志文件,每个请求都写一条,结果压测时发现吞吐量降了不少。后来加了 Log::write() 的时候,发现 TP8 的日志本身有异步写入的配置,但默认还是同步。如果你也遇到性能问题,可以改成 thinkfacadeLog::write(..., 'api') 后用队列异步处理,或者把日志写到 Redis 里再定时落盘。我这里只是示例,生产环境建议做异步。

另一个坑是 pathinfo() 在路由模式下可能和实际路由不对应。如果你用了 URL 重写和路由别名,最好用 $request->url() 来取路径,然后自己解析。我的项目是直接用控制器/方法路径,所以没遇到问题。

顺便给中间件加个跨域头

我在做接口的时候,还顺便在中间件里加了跨域响应头。因为 TP8 默认同源限制,前后端分离开发时如果不处理跨域,前端调接口会一堆报错。在中间件返回之前加几个头就行:

$response = $next($request);
$response->header([
    'Access-Control-Allow-Origin'      => '*',
    'Access-Control-Allow-Methods'     => 'GET,POST,PUT,DELETE,OPTIONS',
    'Access-Control-Allow-Headers'     => 'token,Content-Type,Accept',
]);
if ($request->isOptions()) {
    return json('ok')->code(200);
}
return $response;

这段放在 handle 方法里,比 Nginx 配置更灵活,也能解决某些开发环境的跨域 preflight 请求。

最后想说的

中间件这种设计,让我的接口从以前“每个控制器自己重复校验”变成了“一次配置,全局生效”。改动最大的还是那些老接口,删掉了每行开头的 checkLogin,代码短了一截,后来接手的同事也说好维护了。

TP8 的中间件支持不少好用的特性,比如前置/后置行为,我暂时只用了前置和简单登录校验,但已经比以前优雅太多。如果你也为接口鉴权和日志烦恼,不妨试试这个写法。

ThinkPHP8中间件实战:一个请求日志+接口鉴权的简单实现
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请发送邮件1506151422@qq.com联系,将会及时下架删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 thinkphp ThinkPHP8中间件实战:一个请求日志+接口鉴权的简单实现 https://www.taomawang.com/server/thinkphp/2652.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务