ThinkPHP6实现行为日志记录:中间件捕获与定时入库全流程

周末不加班
周末不加班
发布时间:2026-10-15 | 6人浏览过
原创

在Web应用开发中,记录用户的行为日志对于安全审计和问题排查至关重要。本文将介绍如何在ThinkPHP6框架中,利用全局中间件拦截请求并收集关键信息(如操作人、路由、请求参数等),同时为了避免实时写入数据库带来的性能损耗,采用“先写日志文件、后定时入库”的异步处理方案。

结果与目标

我们的目标是构建一个无侵入式的行为日志系统。当用户访问网站时,无需在每个控制器中手动添加日志代码,系统即可自动捕获操作痕迹;并通过后台定时任务定期将这些临时日志数据持久化到关系型数据库中。

准备条件

  1. 已安装并配置好 ThinkPHP6 运行环境。
  2. 具备服务器命令行权限或能够执行 php think 命令。
  3. 拥有可操作的数据库权限以创建新表。

第1步:生成自定义中间件

目的: 创建一个用于拦截HTTP请求并提取行为数据的中间件类。

操作: 在项目根目录终端执行以下命令:

php think make:middleware Behavior

执行成功后,会在 app/middleware 目录下生成 Behavior.php 文件。

第2步:编写中间件逻辑

入口: 打开 app/middleware/Behavior.php。

动作: 修改 handle 方法,加入数据采集逻辑。以下是核心代码示例:

<?php
declare (strict_types = 1);
namespace app\middleware;
use think\facade\Log;

class Behavior
{
    public function handle($request, \Closure $next)
    {
        // 获取当前登录用户信息(需自行实现get_admin_info()函数)
        $admin = get_admin_info();
        
        // 采集基础请求信息
        $method = strtolower($request->method());
        $is_ajax = $request->isAjax();
        $route = $request->pathinfo();
        $req = $_REQUEST;
        
        // 清理敏感或内部Session标识
        unset($req['s'], $req['_session']);  
        $req_data = $req ? json_encode($req) : '';
        
        $data = [
            'admin_id' => $admin['id'] ?? 0,				// 操作人ID
            'admin_user' => $admin['user'] ?? '',			// 操作人用户名
            'route' => $route,							// 操作的路由地址
            'method' => $method,							// GET/POST
            'req_tp' => $is_ajax ? 'ajax' : 'normal',		// 请求类型
            'req_data' => $req_data,   					 // 请求参数(JSON)
            'ip' => $request->ip(),						 // 客户端IP
            'create_time' => time()
        ];
        
        // 注意:此处暂不直接写入DB,而是后续步骤中由日志通道处理
        // Log::channel('behavior')->info(json_encode($data)); 
        
        return $next($request);
    }
}

第3步:配置独立的日志通道

入口: 打开配置文件 config/log.php。

动作: 在 'channels' 数组末尾追加名为 behavior 的新通道配置。此配置旨在将日志写入单一文件,且关闭实时写入以减少IO开销。

// config/log.php
return [
    // ... 其他配置 ...
    'default' => 'syslog',
    'channels' => [
        // ... 原有通道 ...
        
        // 新增行为日志通道
        'behavior' => [
            'type' => 'File',
            'path' => runtime_path() . 'behavior/',  // 日志存放目录
            'single' => 'b',                         // 单一文件模式,文件名前缀为b
            'file_size' => 1024 * 1024 * 10,         // 单个文件大小限制(MB),超出则滚动
            'max_files' => 30,                       // 最大保留文件数
            'realtime_write' => false,               // 关闭实时写入,提高效率
        ],
    ]
];

第4步:注册全局中间件

入口: 打开 app/middleware.php。

动作: 引入并注册刚才创建的 Behavior 中间件,使其对每个请求生效。

<?php
// app/middleware.php
return [
    // \think\middleware\CheckRequestCache::class,
    // \think\middleware\LoadLangPack::class,
    // \think\middleware\SessionInit::class,
    // 注册行为日志全局中间件
    \app\middleware\Behavior::class,
];

第5步:测试日志生成状态

操作: 访问网站的任意页面(例如:/index/index/test?a=1&b=2)。

结果: 检查 runtime/behavior/ 目录,应发现生成的日志文件(如 b.log)。打开该文件,内容格式如下所示,表明数据已成功写入临时文件:

日志JSON示例解析:

  • msg 字段包含了我们在中间件中组装的操作数据结构。
  • admin_user 和 route 验证了用户身份和操作路径被正确捕获。

第6步:实现定时入库脚本

目的: 读取临时日志文件,解析后将数据批量插入数据库,并清空原文件。

入口: 在对应的Controller(建议作为内部API接口)中新建方法 sync_behavior_log。

public function sync_behavior_log()
{
    $path = runtime_path() . 'behavior/b.log';
    
    // 判断文件是否存在且有内容
    if (!file_exists($path) || !filesize($path)) {
        return 'No log file or empty.';
    }

    // 读取文件并按行分割
    $b_file = file_get_contents($path);
    $b_arr = explode(PHP_EOL, $b_file);
    
    $d = [];
    foreach ($b_arr as $b) {
        if (empty(trim($b))) continue;
        
        // 解析日志格式: {"time":"...","type":"...","msg":"{"key":"val"}"}
        $data = json_decode($b, true);
        if (!empty($data['msg'])) {
            // msg本身是JSON字符串,再次解码得到实际业务数据
            $d[] = json_decode($data['msg'], true);
        }
    }
    
    if ($d) {
        try {
            // 批量插入数据库
            Db::name('log_behavior')->insertAll($d);     
            
            // 清空日志文件,完成本轮同步
            file_put_contents($path, '');       
            
            echo 'Success! Synced ' . count($d) . ' logs.';
        } catch (\Exception $e) {
            echo 'Error: ' . $e->getMessage();
        }
    } else {
        echo 'No valid data to sync.';
    }
}

第7步:创建数据表结构

动作: 在数据库中执行以下SQL语句,创建用于存储行为日志的表 log_behavior。

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

DROP TABLE IF EXISTS `log_behavior`;
CREATE TABLE `log_behavior`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `admin_id` int(11) NOT NULL DEFAULT 0 COMMENT '操作用户ID',
  `admin_user` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户名',
  `route` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '访问路由',
  `method` enum('delete','put','post','get') CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'get' COMMENT '请求方式',
  `req_data` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '请求参数JSON',
  `ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'IP地址',
  `create_time` int(11) NOT NULL DEFAULT 0 COMMENT '操作时间戳',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `uid`(`admin_id`) USING BTREE,
  INDEX `route`(`route`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户行为日志表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

第8步:配置服务器定时任务

操作: 在服务器 crontab 中添加定时任务,定期调用上述 API 地址。

建议频率: 每 5 分钟执行一次。

示例:

*/5 * * * * curl http://www.yourdomain.com/index/api/sync_behavior_log &>/dev/null

总结

通过以上步骤,我们实现了基于 ThinkPHP6 的非阻塞式行为日志记录方案。该方案利用中间件自动化数据采集,通过文件系统缓冲降低数据库I/O压力,并最终经由定时任务保证数据的一致性。需要注意的潜在风险包括:如果中间件中依赖的 get_admin_info() 函数失效,可能导致日志中用户信息为空;此外,若日志文件损坏,需人工干预修复。以上就是ThinkPHP6如何使用中间件记录行为日志的详细内容,更多关于ThinkPHP6日志配置的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表