ThinkPHP 6 的 hidden/visible 不支持权限级脱敏,仅控制序列化字段显隐;需在查询(field 动态裁剪)、资源类(Resource 传入用户上下文)、或 withAttr/append 配合访问器中实现角色感知脱敏。

ThinkPHP 6 的 hidden 和 visible 不适用于权限级脱敏
这两个属性只控制序列化输出时字段是否出现,不校验当前用户是否有权查看——比如管理员和普通用户都调用同一个 toArray(),字段照样全暴露。真要按角色/权限动态过滤,得在查询或转换阶段介入。
实操建议:
- 别在模型定义里硬写
protected $hidden = ['id_card', 'phone'],这属于静态掩码,不是权限控制 - 优先把脱敏逻辑下沉到查询构造器或资源类(Resource),确保不同角色拿到的数据结构真正不同
- 如果用
think-modelv6.0.10+,可配合withAttr做运行时字段替换,但注意它不删字段,只改值
用 append + 自定义访问器实现角色感知脱敏
这是最轻量、也最容易失控的方案:在模型里定义一个带权限判断的访问器,再通过 append 动态追加。但它要求每次调用前手动 set 当前用户上下文,否则权限判断就失效。
常见错误现象:user->phone_masked 返回原始手机号,因为 $this->authUser 是 null 或没初始化
立即学习“PHP免费学习笔记(深入)”;
实操建议:
- 在控制器中显式传入用户实例:
$user->setAuthUser($request->user()) - 访问器内部用
if ($this->authUser && $this->authUser->can('view_phone')) { return $this->phone; } - 避免在
select *后直接append(['phone_masked']),字段已查出,脱敏只是“马后炮”
think-orm 查询时用 field 动态裁剪敏感字段
这才是真正从源头控制权限的方式:根据用户角色,决定 SQL SELECT 中包含哪些字段。既减少网络传输,又杜绝服务端内存残留风险。
使用场景:列表页、详情页、导出接口等需严格区分字段可见性的场合
实操建议:
- 定义权限字段映射表,例如:
['admin' => ['id', 'name', 'phone', 'id_card'], 'user' => ['id', 'name']] - 查询前拼接字段数组:
UserModel::field($allowedFields)->select() - 注意
field不影响关联查询,默认仍会查出关联模型全部字段,需对关联单独处理 - 若用
with加载关联,记得在关联定义里也做字段限制,比如User::has('profile', 'profile.user_id=user.id')->field(['nickname'])
资源类(Resource)是脱敏逻辑最干净的落点
ThinkPHP 6.1+ 支持类似 Laravel Resource 的数据包装机制,toArray() 方法天然适合做字段级权限裁剪——它发生在数据已查出、但尚未响应之前,可控性强,且与业务逻辑解耦。
容易踩的坑:Resource 实例默认没有用户上下文,$this->resource 只是模型对象,不带请求信息
实操建议:
- 构造 Resource 时传入用户:
new UserResource($user, $request->user()) - 在 Resource 类里保存
$this->authUser,并在toArray()中按需返回字段 - 不要在 Resource 里调用
$this->resource->hidden = [...],这会污染原始模型状态 - 复杂权限(如“仅能看自己手机号”)建议抽成独立服务,Resource 只负责调用,不写判断逻辑
字段级权限过滤不是加个配置就能生效的事,它必须和认证上下文、查询时机、数据流转阶段咬合。漏掉任意一环,脱敏就变成障眼法。



















