thinkphp模型中用append+getxxxattr定义只读计算字段,避免误写;空查询日志需在查库前埋点并加缓存健康校验,空标记用短时效布尔值区分业务与配置问题。

ThinkPHP 模型里怎么加只读计算字段(不存数据库)
计算字段不能直接写进数据库,但又想在模型实例上像普通字段一样用 $model->age 这样取值——关键不是“能不能”,而是“别让它被当成可写字段误操作”。ThinkPHP 的 getAttr 和 setAttr 钩子能拦截访问,但更稳的方式是用 append + get<em>XxxAttr</em> 方法组合。
常见错误是把计算逻辑塞进 setAttr 里,结果新增/更新时被意外触发;或者没配 append,导致序列化或 toArray() 时字段直接消失。
- 在模型中定义
protected $append = ['full_name'] - 添加方法
getFullNameAttr(),返回拼接后的字符串,不要带参数、不要修改任何属性 - 避免在该方法里调用
save()或触发数据库写操作,否则会引发隐式写入 - 如果计算依赖关联模型,记得用
with()预加载,否则 N+1 查询就来了
缓存穿透时如何记录空查询(比如查不到的 user_id=999999)
缓存穿透本质是“查不到还反复打 DB”,ThinkPHP 默认不会帮你记这种空结果。得在缓存未命中后、查库前手动埋点,而不是等查完再判断是否为空——否则查库本身已经发生,日志也晚了。
典型场景:用户中心接口按 ID 查用户,ID 是伪造的或已被删除,缓存没存、DB 也没查到,但每次请求都走一遍数据库。
- 用
Cache::get($key, function () use ($id) { ... })包裹查询逻辑 - 在闭包里先执行 DB 查询(如
UserModel::find($id)),再判断结果是否为 null - 如果是 null,立刻写一条日志:
Log::info('cache_miss_empty', ['key' => $key, 'id' => $id]) - 别用
Cache::has()再查一次,那会多一次 Redis 请求,反而加重穿透
为什么用 Cache::remember 会绕过空缓存日志
因为 Cache::remember 的底层逻辑是:先尝试 get,没命中才执行回调并自动 set。你没法在“没命中且回调执行前”插日志——回调里查完 DB 才知道是不是空,此时穿透已经发生。
性能影响很明显:用 remember 查空值,Redis 少一次写,但 DB 多一次无意义查询;而自己控制流程,可以对空结果主动写一个短时效的空标记(如 Cache::set($key . '_null', true, 60)),彻底挡掉后续请求。
- 空标记 key 要和业务 key 区分,比如加
_null后缀,避免覆盖真实数据 - 空标记过期时间不宜过长(建议 1–5 分钟),防止真实数据插入后无法及时生效
- 别在空标记里存复杂结构,就用布尔值或字符串
"null",减少序列化开销
日志里怎么区分「真没数据」和「缓存没配好」
光记 cache_miss_empty 不够,容易把配置错误当成业务现象。比如缓存驱动没启、Redis 连不上、key 命名规则写错,都会导致“查不到”,但根本不是穿透问题。
最简单的区分方式:加一层缓存可用性校验。不是查业务 key,而是查一个固定测试 key。
- 在日志前加判断:
if (!Cache::store('redis')->get('cache_health_test')) { Log::warning('cache_unavailable'); return null; } - 确保部署时有脚本定期写入
cache_health_test,比如每分钟一次 - 日志字段里带上
cache_driver和cache_hit(true/false),方便聚合分析 - 注意:别在每次请求都执行健康检查,只在空查询日志触发时顺带查一次
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











