Laravel 8 求助:自动存储模型字段变更历史为JSON格式(Trait复用)
Laravel 8 模型变更历史自动记录实现(JSON格式存储)
实现思路
通过Laravel Trait封装通用逻辑,监听模型的saved事件(覆盖创建和更新操作),自动收集变更字段信息,追加到同表的history JSONB列中,支持所有模型复用。
步骤1:创建通用Trait
在app/Traits目录下创建RecordsHistory.php,实现核心逻辑:
<?php namespace App\Traits; use Illuminate\Support\Facades\Auth; trait RecordsHistory { public static function bootRecordsHistory() { static::saved(function ($model) { // 读取模型自定义的忽略字段,默认排除系统字段和history本身 $ignoreFields = property_exists($model, 'ignoreHistoryFields') ? $model->ignoreHistoryFields : ['id', 'created_at', 'updated_at', 'history']; // 获取本次变更的字段集合,移除忽略项 $changedFields = array_diff_key($model->getChanges(), array_flip($ignoreFields)); if (empty($changedFields)) { return; // 无有效变更,跳过记录 } // 构建历史记录条目 $historyEntry = [ 'timestamp' => now()->toDateTimeString(), 'user_id' => Auth::check() ? Auth::id() : null, 'user_name' => Auth::check() ? Auth::user()->name : 'system', 'changes' => [] ]; // 填充每个变更字段的原始值与新值 foreach ($changedFields as $field => $newValue) { $historyEntry['changes'][] = [ 'field' => $field, 'old_value' => $model->getOriginal($field), 'new_value' => $newValue ]; } // 读取现有历史记录,解码为数组(兼容空值) $existingHistory = json_decode($model->history, true) ?? []; // 追加新记录 $existingHistory[] = $historyEntry; // 禁用事件避免循环触发,更新history字段 $model->withoutEvents(function () use ($model, $existingHistory) { $model->update([ 'history' => json_encode($existingHistory) ]); }); }); } }
步骤2:数据库表添加history列
针对需要记录历史的模型表,添加history字段(PostgreSQL推荐使用jsonb类型,支持高效查询和索引):
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class AddHistoryColumnToYourTable extends Migration { public function up() { Schema::table('your_table_name', function (Blueprint $table) { $table->jsonb('history')->default('[]'); }); } public function down() { Schema::table('your_table_name', function (Blueprint $table) { $table->dropColumn('history'); }); } }
执行迁移命令:
php artisan migrate
步骤3:模型中使用Trait
在需要启用历史记录的模型中引入Trait即可:
<?php namespace App\Models; use App\Traits\RecordsHistory; use Illuminate\Database\Eloquent\Model; class YourModel extends Model { use RecordsHistory; // 可选:自定义需要忽略的字段(比如敏感字段) // protected $ignoreHistoryFields = ['id', 'created_at', 'updated_at', 'history', 'password']; }
关键细节说明
- 避免循环触发:使用
withoutEvents包裹update操作,防止更新history字段时再次触发saved事件导致无限循环 - 未登录场景处理:控制台命令或无认证的API请求中,用户信息会设为
user_id: null、user_name: system,可根据业务调整 - 字段忽略机制:默认排除系统字段和
history本身,模型可通过$ignoreHistoryFields属性自定义忽略项 - JSONB类型优势:PostgreSQL的
jsonb比json支持索引、更快的查询性能,适合存储结构化历史数据
内容的提问来源于stack exchange,提问作者Lokesh M
相关产品推荐
相关产品推荐

