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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 06:40:52