ThinkPHP6.0中需通过模型hidden属性屏蔽敏感字段或用getAttr获取器实现动态脱敏,同时避免使用getData()、json_encode($model)及未脱敏的日志记录。

ThinkPHP6.0中输出用户数据时,手机号、身份证号、银行卡号等字段明文出现在API响应里,前端直接渲染就等于泄露隐私——这不是测试环境疏忽,是模型层没做字段控制或脱敏逻辑没介入序列化流程。
用模型hidden属性一键屏蔽敏感字段
在User模型类顶部直接声明要隐藏的字段名,所有调用toArray()、toJson()的地方自动过滤。
打开app/model/User.php,在class User extends Model { }内部添加:
protected $hidden = ['password', 'id_card', 'bank_card', 'email'];
立即学习“PHP免费学习笔记(深入)”;
【字段名必须与模型属性名一致】比如数据库字段是user_email,但模型定义了getEmailAttr()获取器并返回'email',那这里就得写'email',而不是'user_email';写错就完全不起作用。
这个设置对关联模型无效——如果with('profile')加载了Profile模型,Profile里的hidden不会被继承,需单独在Profile模型中定义。
对特定字段做动态脱敏(非全字段隐藏)
当需要保留字段但模糊显示内容时,比如手机号显示为138****1234,用模型获取器(getAttr)实现最稳妥。
在User模型中添加:
public function getMobileAttr($value) { return $value ? substr($value, 0, 3) . '****' . substr($value, -4) : ''; }
public function getIdCardAttr($value) { return $value ? substr($value, 0, 6) . '******' . substr($value, -4) : ''; }
这样调用$user->mobile或$user->toArray()时,mobile字段自动变成脱敏后结果;且不影响数据库原始值读取和写入。
注意:获取器返回值也会被hidden过滤——如果同时设置了hidden = ['mobile'],那这个getMobileAttr根本不会触发。二者不可共存于同一字段。
接口层统一脱敏:用toArray()传参控制
不同接口对同一模型有不同脱敏需求,比如管理后台要看到完整身份证号,而小程序接口必须脱敏——这时别改模型,用运行时参数。
第一步:在控制器中构造白名单数组
$visible = ['id', 'username', 'avatar'];
第二步:判断接口类型,动态追加字段
if ($isMiniProgram) { $visible = array_merge($visible, ['mobile_masked', 'id_card_masked']); }
第三步:调用toArray并注入可见字段
$data = $user->toArray(['visible' => $visible]);
第四步:在模型中定义对应获取器
public function getMobileMaskedAttr() { return $this->getMobileAttr($this->mobile); }
这一步必须做,否则'mobile_masked'字段不存在,toArray会返回null而非脱敏值。
避免脱敏失效的三个关键检查点
检查是否用了$data = $user->getData() → 它返回原始数据库字段数组,【完全无视hidden、visible、获取器】,所有脱敏逻辑全部失效。
检查是否写了echo json_encode($user) → 这走PHP默认序列化,不触发ThinkPHP字段过滤,敏感字段原样吐出。
检查是否在中间件或事件里提前调用了input()或param()并记录日志 → 如果没做预脱敏,日志里已存明文,后续再脱敏也晚了。



















