ThinkPHP中间件与事件系统实战:构建可扩展的API鉴权与日志体系

2026-05-14 0 214

在复杂的Web应用中,中间件事件系统是解耦业务逻辑的两大神器。ThinkPHP 6+内置了强大的中间件机制和事件系统,让开发者可以轻松实现请求过滤、权限验证、日志记录等横切关注点。本文通过构建一个API鉴权请求日志系统,完整演示如何利用中间件和事件系统构建可扩展的架构。

一、为什么需要中间件和事件系统?

传统方式中,鉴权和日志逻辑散落在控制器中,导致代码重复且难以维护。中间件可以在请求到达控制器之前执行通用逻辑,事件系统则能在特定时刻触发异步操作(如记录日志、发送通知)。两者结合,可以让控制器专注于业务逻辑。

二、项目结构设计

我们基于ThinkPHP 6+创建项目,目录结构如下:

├─ app
│   ├─ controller
│   │   ├─ Index.php
│   │   └─ User.php
│   ├─ middleware
│   │   ├─ AuthMiddleware.php      # 鉴权中间件
│   │   └─ LogMiddleware.php       # 日志中间件
│   ├─ event
│   │   ├─ RequestEvent.php        # 请求事件类
│   │   └─ RequestListener.php     # 请求事件监听器
│   ├─ model
│   │   └─ User.php
│   └─ service
│       └─ AuthService.php         # 鉴权服务
├─ config
│   └─ middleware.php              # 中间件配置
├─ route
│   └─ app.php                     # 路由定义
└─ .env
    

三、中间件实战:API鉴权

1. 创建鉴权中间件

<?php
namespace appmiddleware;

use appserviceAuthService;
use thinkRequest;
use thinkResponse;

class AuthMiddleware
{
    /**
     * 处理请求
     * @param Request $request
     * @param Closure $next
     * @return Response
     */
    public function handle(Request $request, Closure $next)
    {
        // 获取token(从Header或参数中)
        $token = $request->header('Authorization');
        if (!$token) {
            $token = $request->param('token');
        }
        
        if (!$token) {
            return json(['code' => 401, 'message' => '未提供认证令牌'], 401);
        }
        
        // 调用鉴权服务验证token
        $authService = new AuthService();
        $userInfo = $authService->verifyToken($token);
        
        if (!$userInfo) {
            return json(['code' => 401, 'message' => '令牌无效或已过期'], 401);
        }
        
        // 将用户信息绑定到请求对象,方便后续控制器使用
        $request->userInfo = $userInfo;
        
        // 继续执行请求
        return $next($request);
    }
}
    

2. 鉴权服务类

<?php
namespace appservice;

use appmodelUser;
use FirebaseJWTJWT;
use FirebaseJWTKey;

class AuthService
{
    private string $secretKey = 'your-secret-key-here';
    
    /**
     * 生成JWT令牌
     */
    public function generateToken(array $userInfo): string
    {
        $payload = [
            'iss' => 'thinkphp-api',     // 签发者
            'iat' => time(),              // 签发时间
            'exp' => time() + 7200,       // 过期时间2小时
            'uid' => $userInfo['id'],
            'username' => $userInfo['username']
        ];
        
        return JWT::encode($payload, $this->secretKey, 'HS256');
    }
    
    /**
     * 验证JWT令牌
     */
    public function verifyToken(string $token): ?array
    {
        try {
            $decoded = JWT::decode($token, new Key($this->secretKey, 'HS256'));
            return (array) $decoded;
        } catch (Exception $e) {
            return null;
        }
    }
}
    

3. 路由配置与中间件注册

// route/app.php
use thinkfacadeRoute;

// 公开路由(无需登录)
Route::post('login', 'User/login');

// 需要鉴权的路由组
Route::group('api', function () {
    Route::get('user/info', 'User/getUserInfo');
    Route::post('user/update', 'User/update');
})->middleware(appmiddlewareAuthMiddleware::class);
    

四、事件系统实战:请求日志

我们希望每次API请求都记录日志,但不希望在中间件或控制器中直接写日志代码。使用事件系统可以解耦。

1. 定义请求事件类

<?php
namespace appevent;

use thinkRequest;

class RequestEvent
{
    public Request $request;
    public array $responseData;
    public float $startTime;
    public float $endTime;
    
    public function __construct(Request $request, array $responseData, float $startTime, float $endTime)
    {
        $this->request = $request;
        $this->responseData = $responseData;
        $this->startTime = $startTime;
        $this->endTime = $endTime;
    }
}
    

2. 创建监听器

<?php
namespace appevent;

use thinkfacadeLog;

class RequestListener
{
    /**
     * 处理请求日志
     */
    public function handle(RequestEvent $event): void
    {
        $request = $event->request;
        $duration = round(($event->endTime - $event->startTime) * 1000, 2); // 毫秒
        
        $logData = [
            'method'    => $request->method(),
            'url'       => $request->url(true),
            'params'    => $request->param(),
            'response'  => $event->responseData,
            'duration'  => $duration . 'ms',
            'timestamp' => date('Y-m-d H:i:s'),
            'user_id'   => $request->userInfo['uid'] ?? 0,
        ];
        
        // 写入日志文件
        Log::write(json_encode($logData, JSON_UNESCAPED_UNICODE), 'request');
        
        // 也可以写入数据库或发送到消息队列
        // Db::name('request_log')->insert($logData);
    }
}
    

3. 注册事件与监听器

// config/event.php
return [
    'bind' => [
        'RequestEvent' => 'appeventRequestEvent',
    ],
    
    'listen' => [
        'RequestEvent' => [
            'appeventRequestListener',
        ],
    ],
];
    

4. 在控制器中触发事件

<?php
namespace appcontroller;

use appeventRequestEvent;
use thinkfacadeEvent;
use thinkRequest;

class User extends BaseController
{
    public function getUserInfo(Request $request)
    {
        $startTime = microtime(true);
        
        // 业务逻辑
        $data = ['username' => $request->userInfo['username'], 'email' => 'test@example.com'];
        
        $endTime = microtime(true);
        
        // 触发请求日志事件
        Event::trigger('RequestEvent', new RequestEvent($request, $data, $startTime, $endTime));
        
        return json(['code' => 0, 'data' => $data]);
    }
}
    

五、中间件与事件协同工作流

整个请求流程如下:

  1. 客户端发送请求到 /api/user/info
  2. AuthMiddleware 拦截请求,验证JWT令牌,将用户信息绑定到 $request->userInfo
  3. 请求到达 UserController::getUserInfo,执行业务逻辑
  4. 控制器触发 RequestEvent 事件,传递请求和响应数据
  5. RequestListener 监听事件,将日志写入文件或数据库
  6. 返回JSON响应给客户端

这样,鉴权和日志逻辑被完美解耦,控制器只需关注业务,中间件关注横切关注点,事件系统处理异步操作。

六、扩展:使用中间件记录请求耗时

我们还可以创建一个专门的日志中间件,记录所有请求的耗时,而不需要每个控制器手动触发事件。

<?php
namespace appmiddleware;

use thinkRequest;
use thinkResponse;
use thinkfacadeLog;

class LogMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        $startTime = microtime(true);
        
        // 执行请求
        $response = $next($request);
        
        $endTime = microtime(true);
        $duration = round(($endTime - $startTime) * 1000, 2);
        
        // 记录日志
        Log::write(sprintf(
            '[%s] %s %s - %dms',
            $request->method(),
            $request->url(true),
            $response->getCode(),
            $duration
        ), 'access');
        
        return $response;
    }
}
    

config/middleware.php 中注册为全局中间件:

return [
    'global' => [
        appmiddlewareLogMiddleware::class,
    ],
];
    

这样所有请求都会自动记录耗时日志,无需修改任何控制器代码。

七、最佳实践与注意事项

  • 中间件顺序:在路由中注册中间件时,注意顺序。例如鉴权中间件应该在日志中间件之后,因为日志可能需要记录用户信息。
  • 事件异步化:如果日志写入数据库较慢,可以使用消息队列(如Redis)异步处理,避免阻塞响应。
  • 依赖注入:中间件和监听器支持依赖注入,可以方便地使用模型、服务等。
  • 异常处理:在中间件中抛出异常会被ThinkPHP的异常处理机制捕获,可以统一返回JSON错误。

八、总结

通过中间件和事件系统,我们构建了一个可扩展的API鉴权与日志体系。中间件负责请求过滤,事件系统负责解耦业务逻辑。这套架构可以轻松扩展到更多场景:如接口频率限制、权限验证、数据校验等。

ThinkPHP的中间件和事件系统设计优雅,掌握它们能让你的应用架构更清晰、更易维护。现在就去重构你的项目吧!


本文为原创技术教程,代码基于ThinkPHP 6.1+测试通过。建议在实际项目中结合Composer安装JWT库:composer require firebase/php-jwt

ThinkPHP中间件与事件系统实战:构建可扩展的API鉴权与日志体系
收藏 (0) 打赏

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

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

淘吗网 thinkphp ThinkPHP中间件与事件系统实战:构建可扩展的API鉴权与日志体系 https://www.taomawang.com/server/thinkphp/1794.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

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

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