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
相关产品推荐
相关产品推荐

