Laravel模型历史怎么追踪

wen PHP项目 1

Laravel模型历史追踪:从审计日志到高效数据回溯的完整指南

目录导读


为什么需要追踪模型历史?

在开发企业级Laravel应用时,数据变更追踪是一个硬性需求,无论是电商平台的订单价格修改、用户信息的违规操作追溯,还是CMS内容的历史版本恢复,缺少模型历史记录都会带来巨大风险。

Laravel模型历史怎么追踪

核心场景包括:

  • 审计合规(如金融、医疗行业要求记录每次数据变更)
  • 操作回溯与错误恢复
  • 用户行为分析(谁在什么时间修改了什么字段)
  • 数据冲突排查(多人协作场景)

主流追踪方案对比

方案 复杂度 性能 灵活性 适用场景
Eloquent事件+自定义日志 简单追踪需求
laravel-auditing扩展包 极高 企业级生产环境
数据库触发器+版本表 极高 不依赖框架的强审计
Spatie/laravel-activitylog 通用活动日志

Eloquent事件监听与日志表

这是最基础也是最可控的方案,通过Laravel模型事件(createdupdateddeleted)自动记录变更。

步骤1:创建审计日志表

Schema::create('model_audits', function (Blueprint $table) {
    $table->id();
    $table->morphs('auditable'); // 多态关联
    $table->string('event'); // created/updated/deleted
    $table->json('old_values')->nullable();
    $table->json('new_values')->nullable();
    $table->foreignId('user_id')->nullable();
    $table->string('ip_address', 45)->nullable();
    $table->timestamps();
});

步骤2:在模型中使用Boot方法

protected static function booted()
{
    static::updated(function ($model) {
        $changes = $model->getChanges();
        if (!empty($changes)) {
            ModelAudit::create([
                'auditable_id' => $model->id,
                'auditable_type' => get_class($model),
                'event' => 'updated',
                'old_values' => json_encode(array_intersect_key($model->getOriginal(), $changes)),
                'new_values' => json_encode($changes),
                'user_id' => auth()->id(),
                'ip_address' => request()->ip(),
            ]);
        }
    });
}

优缺点:

  • 优点:完全自定义,不依赖第三方包
  • 缺点:需要维护大量代码,性能关注不足

Laravel Auditing扩展包详解

OwenIt/laravel-auditing是目前GitHub上最受欢迎的Laravel审计包,Star超过3.3k,支持Laravel 5.4+到11.x版本。

安装与配置

composer require owen-it/laravel-auditing
php artisan vendor:publish --provider="OwenIt\Auditing\AuditingServiceProvider"
php artisan migrate

在模型中启用

use OwenIt\Auditing\Contracts\Auditable;
class Product extends Model implements Auditable
{
    use \OwenIt\Auditing\Auditable;
    // 指定不追踪的字段
    protected $auditExclude = ['updated_at', 'remember_token'];
    // 自定义审计触发器
    protected $auditEvents = [
        'created',
        'updated',
        'deleted',
        'restored'  // 软删除恢复
    ];
}

获取历史记录

$product = Product::find(1);
$audits = $product->audits; // 获取所有审计记录
foreach ($audits as $audit) {
    echo $audit->user->name;      // 操作人
    echo $audit->event;           // created/updated/deleted
    echo $audit->old_values;      // 旧值数组
    echo $audit->new_values;      // 新值数组
    echo $audit->created_at;      // 操作时间
}

高级配置(config/auditing.php)

'user' => [
    'guards' => ['web', 'api'],  // 多认证驱动支持
    'resolver' => null,           // 自定义用户解析器
],
'audit_driver' => 'database',   // 支持database, mongodb等
'prune_after' => 90,            // 自动清理90天前的审计日志(需配合命令)

OwenIt/laravel-auditing高级用法

记录关联关系变更

protected $auditEvents = [
    'pivotAttached',
    'pivotDetached',
    'pivotUpdated',
];
// 当产品关联分类时自动记录
$product->categories()->attach([1,2,3]);

多态审计

use OwenIt\Auditing\Contracts\Auditable;
class Comment extends Model implements Auditable
{
    use \OwenIt\Auditing\Auditable;
    // 通过morphMany关联,自动追踪所有可审核模型
    public function auditable()
    {
        return $this->morphTo();
    }
}

自定义审计解析器

// 在AppServiceProvider中绑定
$this->app->bind(
    \OwenIt\Auditing\Contracts\AuditableResolver::class,
    CustomAuditableResolver::class
);

审计数据清理

// 定时任务 - 每天凌晨清理
$schedule->command('audit:prune')->daily();

数据库触发器与版本表

对于极致性能需求,可以使用MySQL触发器直接在数据库层记录变更。

创建版本记录表

CREATE TABLE product_versions (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    product_id BIGINT NOT NULL,
    version INT NOT NULL DEFAULT 1,
    data JSON,
    created_by INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_product_version (product_id, version)
);

创建触发器

CREATE TRIGGER after_product_update 
AFTER UPDATE ON products
FOR EACH ROW
BEGIN
    INSERT INTO product_versions (product_id, version, data, created_by)
    VALUES (
        OLD.id,
        (SELECT COALESCE(MAX(version), 0) + 1 FROM product_versions WHERE product_id = OLD.id),
        JSON_OBJECT(
            'name', JSON_OBJECT('old', OLD.name, 'new', NEW.name),
            'price', JSON_OBJECT('old', OLD.price, 'new', NEW.price)
        ),
        @current_user_id
    );
END;

注意: 这种方式需要结合Laravel中间件在请求开始前设置用户ID(如DB::statement('SET @current_user_id = ?', [auth()->id()]))。


性能优化与注意事项

索引优化

// 为auditable_id和auditable_type建立复合索引
$table->index(['auditable_id', 'auditable_type']);

限制追踪字段

// 只追踪关键字段
protected $auditInclude = ['name', 'price', 'status'];

异步审计

对于高并发场景,可以将审计记录推入队列:

// config/auditing.php
'queue' => [
    'connection' => 'redis',
    'queue' => 'audits',
    'delay' => 0,
],

数据膨胀处理

  • 使用分区表(按月份分区)
  • 定期归档到数据仓库
  • 设置保留期限

常见问题QA

Q1: 审计日志表增长太快怎么办?

A: 结合audit:prune命令定期清理,同时考虑使用$auditInclude只追踪核心字段,若数据量极大,建议使用分表存储或转移到专门的日志系统(如ELK)。

Q2: 如何获取某个时间点的模型快照?

A: 遍历审计记录逆向推断。

$audits = $product->audits()->where('created_at', '<=', $targetTime)->get();
$snapshot = new Product();
foreach ($audits as $audit) {
    $snapshot->fill(array_merge($audit->old_values, $audit->new_values));
}

更推荐使用spatie/versionablempociot/versionable这类专门做版本管理的包。

Q3: 软删除模型如何追踪恢复操作?

A: 确保在模型中添加restored事件监听:

protected $auditEvents = ['created', 'updated', 'deleted', 'restored'];
// OwenIt包会自动处理软删除场景的restored事件

Q4: 审计记录中user_id为空怎么办?

A: 检查config/auditing.php中的user.resolver配置,如果是API无状态认证,需要手动设置:

\OwenIt\Auditing\Facades\Auditing::setCurrentResolver(function () {
    return auth('api')->id(); // 或通过JWT令牌解析
});

Q5: 能否追踪关联模型的同步变更?

A: 支持,但需要显式声明:

// 在父模型中设置
protected $auditEvents = ['pivotAttached', 'pivotDetached'];
// 然后在关联操作时自动记录
$order->products()->sync([1 => ['quantity' => 2], 2 => ['quantity' => 1]]);

总结与最佳实践

  1. 中小项目:直接使用OwenIt/laravel-auditing,配置简单且功能完整。
  2. 企业级系统:结合队列驱动和分表策略,必要时添加内存缓存(如Redis)存储最新审计记录。
  3. 微服务架构:考虑使用事件驱动(如Laravel Broadcast)将审计数据发布到统一的事件总线。
  4. 合规要求高:采用数据库触发器+独立审计服务器方案,确保数据不可篡改。

最终建议: 从Laravel Auditing扩展包开始,它经过了大规模生产环境的验证,社区活跃,支持Laravel 5到11的所有版本,是模型历史追踪的首选方案,对于特殊业务逻辑,再通过自定义事件监听和中间件进行补充。

抱歉,评论功能暂时关闭!