最近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":"修改"
}
]