tp6.0中应优先使用before_write/after_write模型事件实现审计,而非observer;因后者无法覆盖批量更新、软删除等场景,且不支持事务回滚日志清理。

TP6.0 中实现“自动写入更新人 + 时间戳”的审计需求,不推荐用模型观察者(Observer),而应优先使用 模型事件(Model Events)。因为观察者机制在 TP6 中已弱化,且无法覆盖批量更新、软删除、关联保存等关键路径,稳定性远不如原生模型事件。
为什么不用模型观察者?
TP6 的 Observer 并非 Laravel 那样的完整生命周期监听器,它本质是基于事件的简易封装,且默认未注册常用事件监听。更重要的是:
- Db::table()->update()、Model::destroy()、Model::update() 等操作会绕过 Observer
- 无法可靠区分 insert / update / delete 操作类型
- 不支持事务内回滚时的日志清理,审计日志易失真
正确做法:用 before_write + after_write 事件
这是 TP6 官方推荐、覆盖所有写入路径的稳定方案。需在模型中定义事件监听逻辑:
- before_write:拿到待写入数据,可注入 operator_id、ip、user_agent 等审计字段
- after_write:确认写入成功后,再写入独立的 AuditLog 表(避免主表事务失败导致日志残留)
示例(User 模型):
protected static function init()
{
self::beforeWrite(function($model) {
// 判断操作类型
$isInsert = empty($model->id) || !$model->exists();
$model->update_time = time(); // 显式赋值,避开 autoWriteTimestamp 干扰
$model->operator_id = context_get_user_id() ?: 0; // 从 Token/请求上下文取,非 session
$model->operator_ip = request()->ip();
});
self::afterWrite(function($model, $result) {
if ($result) {
\app\model\AuditLog::create([
'table_name' => 'user',
'record_id' => $model->id,
'action' => $model->exists() ? 'update' : 'insert',
'operator_id' => $model->operator_id,
'ip' => $model->operator_ip,
'created_at' => date('Y-m-d H:i:s'),
]);
}
});
}
审计日志表必须独立建模且禁用自动时间戳
为避免循环触发或类型冲突,AuditLog 模型要严格隔离:
- 显式关闭自动写入:
protected $autoWriteTimestamp = false; - 时间字段用
date('Y-m-d H:i:s')或time()手动传入,不依赖模型配置 - operator_id、ip 等字段禁止设为
$readonly或$auto,全部显式赋值 - 日志写入建议加 try-catch,失败也不影响主业务(fail-safe)
动态控制时间戳与操作人写入
某些场景(如仅更新阅读数)需跳过审计字段写入:
- 临时关闭时间戳:
$user->isAutoWriteTimestamp(false)->save(); - 跳过 operator 注入:在 before_write 中判断字段变更,如
!$model->isDirty(['read'])则不设置 operator_id - 批量操作时,可用
foreach单条处理并注入上下文,避免丢失操作人信息











