ThinkPHP6 模型关联查询:我写了 `withJoin` 却漏看 `field` 的坑,结果生产环境爆出一整张用户表

站长杂谈 15 浏览 0 回复 返回上级

昨晚翻旧项目的 git 记录,看到一段自己两年前写的代码,血压又上来了。当时接了个需求:文章列表页要显示作者昵称和头像,但作者表里有手机号、真实姓名、身份证号这些敏感字段。我拍着胸脯说"用关联查询就行,简单",结果上线第二天就被安全组约谈——列表接口把用户整张表的数据全吐出去了。

先上我当时写的"自信代码":

// 错误示范:withJoin 没控字段,等于裸奔
public function getArticleList()
{
    return Article::withJoin('author', 'LEFT')
        ->where('status', 1)
        ->select();
}

看着挺规矩对吧?`withJoin` 做左关联,条件也加了。但问题就出在 `withJoin` 的第二个参数我从来没细看过文档。ThinkPHP6 的 `withJoin` 如果不指定 `field`,默认会把关联模型的所有字段全拉进来。更坑的是,因为用了 `LEFT JOIN`,SQL 执行计划里这些字段混在主查询里,连 ORM 的隐藏属性保护都绕过去了——`hidden()` 和 `visible()` 对这种 join 场景根本不起作用。

当时接口返回的数据大概长这样,我截了个当时的日志:

{
  "id": 1,
  "title": "建站心得",
  "author_id": 42,
  "author__id": 42,
  "author__nickname": "老站长",
  "author__avatar": "https://...",
  "author__phone": "138****1234",  // 炸了
  "author__real_name": "张三",      // 彻底炸了
  "author__id_card": "110101......" // 我可以去领盒饭了
}

修复过程倒是快,但复盘的时候我发现自己对"关联查询的字段控制"理解完全是碎片化的。现在我的写法分两种场景,贴出来给兄弟们参考:

场景一:只取展示字段,敏感数据碰都不要碰

// 正确写法1:withJoin 显式指定 field
public function getArticleList()
{
    return Article::withJoin([
        'author' => ['id', 'nickname', 'avatar']
    ], 'LEFT')
    ->where('status', 1)
    ->select();
}

注意这里 `withJoin` 的第一个参数要写成数组形式,键是关联名,值是要取的字段数组。这个语法我当初看了三遍文档才确认,因为 `with` 和 `withJoin` 的参数结构不完全一样,混着用容易抄错。

场景二:关联数据要二次处理,或者字段太多不想手写

// 正确写法2:用 with 预查询 + 模型里定好可见字段
// Article 模型里
public function author()
{
    return $this->belongsTo(User::class, 'author_id', 'id')
        ->visible(['id', 'nickname', 'avatar']);
}

// 控制器里
public function getArticleList()
{
    return Article::with(['author'])
        ->where('status', 1)
        ->select();
}

这种走预查询(IN 语句),性能上比 join 多一次查询,但模型层的 `visible` 能管住字段白名单,而且后续想加缓存、做数据脱敏都方便。我现在默认用这种,除非列表页明确要按作者字段排序才考虑 join。

还有个更隐蔽的坑:如果关联模型里你写了 `append` 属性或者获取器,用 `withJoin` 的时候这些也会跟着执行。我有一次在 User 模型里加了个 `get_balance` 获取器,本来只是算虚拟币余额,结果文章列表接口每个作者都触发了一次钱包查询,QPS 直接爆炸。后来改成 `with` 预查询,配合 `withCache` 才压下去。

最后说个检查技巧:开发环境我把数据库日志开了 `DB::listen`,每次接口请求完都扫一眼实际执行的 SQL。如果看到 `SELECT *` 或者关联表字段明显过多,立刻回溯。这招比代码审查快,也比上线后抓包安全。

你们有没有被 ORM 的"便利性"背刺过?我现在的原则是:任何关联查询,字段必须显式白名单,宁可多写两行,不留裸奔隐患。

评论0
回复 · 0
还没有回复
微信客服 微信客服