必须用$model->getOrigin()取旧值、$model->getData()取新值做键值比对,禁用toArray();where()->update()不触发事件,需改用find()+save()或手动写日志;敏感字段须白名单过滤,JSON字段diff前需json_encode()标准化。

直接在模型里写 beforeUpdate 或 afterUpdate 就能拦截修改动作,但多数人栽在“旧值拿不准”“批量更新不触发”“敏感字段没过滤”这三处。
afterUpdate 里怎么安全拿到新旧值对比
必须用 $model->getOrigin() 拿旧值,$model->getData() 拿当前值——别用 $model->toArray() 或 $model->values(),它们可能混入脏数据或格式不一致。
-
getOrigin()返回的是上次find()或select()时从数据库读出的原始状态,未被中间修改污染 -
getData()是当前模型属性快照,包含所有字段(含未变更字段),需手动比对键值差异 - JSON 类型字段要注意:
getOrigin()返回 PHP 数组,而数据库存的是字符串;做 diff 前建议统一用json_encode()标准化再比较
where()->update() 为什么没进事件,怎么补救
ThinkPHP 的 where()->update() 是原生 SQL 批量更新,绕过模型实例生命周期,beforeUpdate/afterUpdate 完全不触发。
- 如果业务允许,改用模型实例方式:先
find()单条,改值,再save() - 若必须批量(如后台导出后统一改状态),得手动调日志逻辑:
Db::name('log')->insertAll($logs),且确保连接独立、不参与主事务 - 别把
where()->update()包进Db::transaction()后再塞日志——事务一回滚,日志就丢了,审计链断掉
敏感字段和自动填充字段怎么过滤不记日志
密码、身份证、token 这类字段,连旧值都不能落库;create_time、update_time 这类框架自动写的字段,记了也没业务意义,反而干扰查询。
- 显式白名单过滤:
$whitelist = ['name', 'email', 'status']; $diff = array_intersect_key($diff, array_flip($whitelist)); - 敏感字段用
isset($diff['password']) && unset($diff['password']);强制剔除,别依赖配置项开关 - 日志表的
created_at字段设为数据库默认CURRENT_TIMESTAMP,禁用 ThinkPHP 的auto_write_timestamp,避免和主表时间戳语义混淆
最易被忽略的是 JSON 字段 diff 时的类型错位——getOrigin() 返回数组,getData() 可能还是原始字符串(尤其用了 json 类型定义但没配 type 转换),不做 json_encode() 对齐就直接 == 判断,永远 diff 不出变化。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











