You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel中利用Observable记录Eloquent模型操作至数据库表

全局记录所有Eloquent模型操作到数据库

没问题,我来帮你搞定全局监听所有Eloquent模型操作并记录到指定数据库表的需求——你已经明确了表结构,接下来咱们一步步实现:

1. 先建日志表的迁移文件

首先运行Artisan命令生成迁移:

php artisan make:migration create_model_operation_logs_table

打开生成的迁移文件,把表结构改成你需要的样子:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('model_operation_logs', function (Blueprint $table) {
            $table->id();
            $table->string('model'); // 存模型的完整类名或者别名
            $table->timestamp('timestamp')->useCurrent(); // 自动记录操作时间
            $table->text('sql_query'); // 执行的SQL语句
            $table->string('transaction_id')->nullable(); // 事务ID
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('model_operation_logs');
    }
};

跑迁移创建表:

php artisan migrate

2. 生成日志模型

接着创建对应的Eloquent模型来操作这个日志表:

php artisan make:model ModelOperationLog

这个模型保持默认就行,不用额外修改。

3. 实现全局模型操作监听

你之前看的Observable是单模型的,咱们要搞全局的,推荐用全局模型观察者的方式,这样所有继承基类的模型都会被监听:

第一步:创建全局观察者类

运行命令生成观察者:

php artisan make:observer GlobalModelObserver --model=Model

这里的Model指你的基础模型类(比如App\Models\Model,如果所有业务模型都继承这个基类的话)。

然后打开GlobalModelObserver,编写监听逻辑:

namespace App\Observers;

use App\Models\ModelOperationLog;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class GlobalModelObserver
{
    // 监听模型创建事件
    public function created(Model $model)
    {
        $this->logOperation($model);
    }

    // 监听模型更新事件
    public function updated(Model $model)
    {
        $this->logOperation($model);
    }

    // 监听模型删除事件
    public function deleted(Model $model)
    {
        $this->logOperation($model);
    }

    // 监听模型恢复事件(软删除场景)
    public function restored(Model $model)
    {
        $this->logOperation($model);
    }

    // 统一处理日志写入的私有方法
    private function logOperation(Model $model)
    {
        // 获取当前数据库连接的事务ID(以MySQL为例,其他数据库可调整SQL)
        $transactionId = DB::selectOne("SELECT CONNECTION_ID() as id")->id;
        
        // 获取最近执行的SQL查询,把参数绑定进去生成完整SQL
        $queryLog = DB::getQueryLog();
        $lastQuery = end($queryLog);
        
        if ($lastQuery) {
            // 把SQL里的?替换成实际参数,字符串参数加引号
            $sql = vsprintf(str_replace('?', '%s', $lastQuery['query']), array_map(function ($param) {
                return is_string($param) ? "'$param'" : $param;
            }, $lastQuery['bindings']));
            
            // 写入日志表
            ModelOperationLog::create([
                'model' => get_class($model),
                'timestamp' => now(),
                'sql_query' => $sql,
                'transaction_id' => $transactionId,
            ]);
        }
    }
}

第二步:注册全局观察者

打开App\Providers\AppServiceProvider,在boot方法里注册这个观察者,这样所有继承Model基类的模型都会被监听:

namespace App\Providers;

use App\Models\Model;
use App\Observers\GlobalModelObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Model::observe(GlobalModelObserver::class);
    }
}

4. 优化事务ID的追踪(可选)

如果想让同一事务里的所有操作共享同一个事务ID(比如你示例里的Task1和Task2用同一个id_3),可以用Laravel的事务事件来生成UUID:

在AppServiceProvider里添加这些代码:

use Illuminate\Database\Events\TransactionBeginning;
use Illuminate\Database\Events\TransactionCommitted;
use Illuminate\Database\Events\TransactionRolledBack;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

// 用静态变量存当前事务的UUID
protected static $currentTransactionId;

public function boot()
{
    // 事务开始时生成UUID
    DB::listen(function (TransactionBeginning $event) {
        static::$currentTransactionId = Str::uuid()->toString();
    });
    
    // 事务提交或回滚后清空ID
    DB::listen(function (TransactionCommitted|TransactionRolledBack $event) {
        static::$currentTransactionId = null;
    });
    
    // 然后把之前观察者里的$transactionId改成static::$currentTransactionId就行
}

这样同一事务内的所有操作都会用同一个UUID作为事务ID,完全匹配你要的效果。

一些注意点

  • 性能问题:全局监听所有操作会增加数据库压力,生产环境建议加个开关,或者只记录关键模型的操作。
  • SQL格式化:示例里的参数替换逻辑适合大部分场景,但遇到JSON、二进制数据可能要调整处理方式。
  • 模型识别:如果你的模型没有统一继承基类,那可以换用DB::listen全局监听SQL,然后通过表名反向映射到模型(需要维护表名和模型的对应关系)。

内容的提问来源于stack exchange,提问作者Remo Fiorentino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:45:14