tp(thinkphp6)记录后台操作日志

2024-11-03 48 0

最近leader让我优化一下之前后台系统的日志操作记录,在网上没有找到一个可用的demo后,决定自己手撸日志

问题复现

旧的实现是在写入数据库后同步新增操作日志的,就好像下面这样。

# 修改数据库
Db::name('la_public_lang')->where('id',$postData['id'])->update($data);
# 同步写入日志
$this->operateLog($this->get_type(),'公共语
言','la_public_lang',$postData['id'],$oldLang,'修改成功',1); // 写入操作日志 

可以看出这样的缺点很明显:

同步写入,速度大打折扣

每次都要调用,冗余代码

后期维护很麻烦

怎么办呢?

这时候就要用到TP框架的模型事件和后置中间件了。

模型事件:https://www.kancloud.cn/manual/thinkphp6_0/1037598

后置中间件:https://www.kancloud.cn/manual/thinkphp6_0/1037493

注意点

官方原话:模型事件只在调用模型的方法生效,使用查询构造器操作是无效的

————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/hzbskak/article/details/118539429

# 也就是说 下面这种清空是不会走到模型事件中去的
PackagesModel::where('id', $id)->update($put);
# 需要这样修改
PackagesModel::find($id)->update($put);

实现过程

在事件中监听写入前后的动作,以及注册一个事件订阅类

app\admin\event.php

<?php
// 事件定义文件
use app\subscribe\admin\Log;

return [
    'bind' => [
    ],

    'listen' => [
        'HttpRun'         => [],
        'HttpEnd'         => [],
        'LogLevel'        => [],
        'LogWrite'        => [],
        'AfterInsert'  => [],
        'BeforeUpdate' => [],
        'AfterUpdate' => [],
        'BeforeDelete' => [],
    ],

    'subscribe' => [
        Log::class
    ],
];

在模型基类中用自定义的事件重写模型事件

app\admin\model\Base.php

public static function onAfterInsert(Model $model)
{
    event('AfterInsert', $model);
}

public static function onBeforeUpdate($model)
{
    event('BeforeUpdate', $model);
}

public static function onAfterUpdate($model)
{
    event('AfterUpdate', $model);
}

public static function onBeforeDelete(Model $model)
{
    event('BeforeDelete', $model);
}

在自定义的事件订阅类中实现具体的业务逻辑

app\admin\subscribe\Log.php

<?php
declare (strict_types=1);

namespace app\admin\subscribe;

use extend\redis\Redis;
use think\Model;
use think\Request;

class Log
{
    // 日志key值
    protected static $aolKey = null;

    // 旧数据
    protected static $beforeData = [];

    /**
     * 新增后
     * @param Model $model
     */
    public static function onAfterInsert(Model $model, Request $request)
    {
        $uid = $request->uid;
        self::$aolKey = !empty($uid) ? config('setting.aol_pre') . $uid : null;

        if (self::$aolKey) {
            $get_data = $model->getData();
            unset($get_data['controller'], $get_data['action'], $get_data['action'], $get_data['list_rows'], $get_data['page']);
            $before = [];
            foreach ($get_data as $key => $value) {
                $before[$key] = '';
            }

            Redis::instance()->rPush(self::$aolKey, json_encode([
                'table_name' => $model->getTable(),
                'fields' => $model->getTableFields(),
                'before' => $before,
                'after' => $get_data,
                'type' => '新增'
            ], JSON_UNESCAPED_UNICODE));
        }
    }

    /**
     * @param Model $model
     * @param Request $request
     */
    public static function onBeforeUpdate(Model $model, Request $request)
    {
        self::$beforeData = $model->where('id', $model->id)->findOrEmpty()->toArray();
    }

    /**
     * 更新后
     * @param Model $model
     */
    public static function onAfterUpdate(Model $model, Request $request)
    {
        $uid = $request->uid;
        self::$aolKey = !empty($uid) ? config('setting.aol_pre') . $uid : null;
        if (self::$aolKey) {
            $before_data = !empty($model->getOrigin()) ? $model->getOrigin() : self::$beforeData;
            $change_data = $model->getChangedData();
            unset($change_data['controller'], $change_data['action'], $change_data['action'], $change_data['list_rows'], $change_data['page']);

            foreach ($change_data as $key => $value) {
                $before[$key] = @$before_data[$key];
            }

            Redis::instance()->rPush(self::$aolKey, json_encode([
                'table_name' => $model->getTable(),
                'fields' => $model->getTableFields(),
                'before' => $before,
                'after' => $change_data,
                'type' => '修改'
            ]));
        }
    }

    /**
     * 删除后
     * @param Model $model
     */
    public static function onBeforeDelete(Model $model, Request $request)
    {
        $uid = $request->uid;
        self::$aolKey = !empty($uid) ? config('setting.aol_pre') . $uid : null;

        if (self::$aolKey) {
            $get_data = $model->getData();
            $before = [];
            foreach ($get_data as $key => $value) {
                $before[$key] = $get_data[$key];
            }

            $after = [];
            foreach ($get_data as $key => $value) {
                $after[$key] = '';
            }

            Redis::instance()->rPush(self::$aolKey, json_encode([
                'table_name' => $model->getTable(),
                'fields' => $model->getTableFields(),
                'before' => $before,
                'after' => $after,
                'type' => '删除'
            ], JSON_UNESCAPED_UNICODE));
        }
    }
}

在后置中间件中处理写入日志的操作

app\admin\middleware\AdminOperateLog.php

<?php
declare (strict_types=1);

namespace app\admin\middleware;

use app\model\AdminOperateLog as AdminOperateLogModel;
use extend\redis\Redis;
use think\App;

class AdminOperateLog
{
    private $app;

    public function __construct(App $app)
    {
        $this->app = $app;
    }

    /**
     *
     * 处理请求
     *
     * @param \think\Request $request
     * @param \Closure $next
     * @return Response
     */
    public function handle($request, \Closure $next)
    {
        $response = $next($request);
        // 添加中间件执行代码

        $data = $response->getData();

        $redis_key = config('setting.aol_pre') . $request->uid;
        $change_data = Redis::instance()->lRange($redis_key, 0, 10);
        if (!empty($change_data)) {
            AdminOperateLogModel::record($this->app, $request->method(),$redis_key, $data, $change_data);
        }

        return $response;
    }
}


设置一个redis 前缀key 名

config\setting.php

<?php

// 普通配置
return [
    // 记录日志的前缀Key
    'aol_pre' => 'ADMIN_OPERATE_LOG_',
];

路由中使用中间件监听

app\admin\route\app.php

Route::group(function () {
})->middleware(AdminOperateLog::class, 'admin')

sql代码

CREATE TABLE `admin_operate_log` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `admin_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '管理员ID',
  `username` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '管理员名字',
  `success` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否成功[0:失败,1:成功]',
  `url` varchar(1500) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '操作页面',
  `title` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '日志标题',
  `method` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '请求方法',
  `token` text CHARACTER SET utf8mb4 COMMENT 'Token',
  `header` text CHARACTER SET utf8mb4 COMMENT 'Header',
  `request_data` text CHARACTER SET utf8mb4 COMMENT '请求数据',
  `response_data` text CHARACTER SET utf8mb4 COMMENT '响应数据',
  `change_data` text COLLATE utf8mb4_unicode_ci COMMENT '变动数据',
  `ip` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'IP',
  `useragent` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'User-Agent',
  `operate_time` timestamp NULL DEFAULT NULL COMMENT '操作时间',
  PRIMARY KEY (`id`),
  KEY `name` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员日志表';

数据库效果

hange_data列的数据

包含变化的数据表名
字段名
变动前后的数据对比

[
    {
        "table_name":"client_contacts",
        "fields":[
            "id",
            "client_id",
            "name",
            "mobile",
            "id_card",
            "gender",
            "age",
            "email",
            "tag",
            "status",
            "province",
            "city",
            "county",
            "address",
            "create_time",
            "update_time",
            "delete_time"
        ],
        "before":{
            "name":"庞洁芳12",
            "update_time":1625537213
        },
        "after":{
            "name":"庞洁芳123",
            "update_time":1625537223
        },
        "type":"修改"
    },
    {
        "table_name":"client",
        "fields":[
            "id",
            "uid",
            "agent_firm_id",
            "type",
            "name",
            "mobile",
            "id_card",
            "gender",
            "age",
            "email",
            "tag",
            "status",
            "province",
            "city",
            "county",
            "address",
            "create_time",
            "update_time",
            "delete_time"
        ],
        "before":{
            "name":"庞洁芳12",
            "update_time":1625537213
        },
        "after":{
            "name":"庞洁芳123",
            "update_time":1625537223
        },
        "type":"修改"
    }
]

 

    相关文章

    PHP常用时间日期处理总结
    使用PHP获取文件夹中所有文件
    thinkphp8中,数据库的字段是 api_node_id ,值是:多个用英文逗号区分,这种应如何来查询准确的数据取出唯一的主键ID?
    ThinkPHP6实现上传图片带水印方法
    ThinkPHP6实现简单的记录网站后台管理操作日志方法
    PHP远程代码执行漏洞(CVE-2024-4577)解决方案(不升级PHP版本)

    发布评论