PHP层脱敏最灵活可控,应封装统一函数(如mask_phone)、用Laravel accessor自动处理、按上下文动态选策略、递归处理JSON嵌套字段,避免规则硬编码与SQL/模板污染。

PHP 查询结果中对手机号、身份证号做脱敏处理
直接在 PHP 层对查询结果做字段脱敏最灵活,也最容易控制粒度。别指望数据库函数(比如 MySQL 的 CONCAT + SUBSTR)一劳永逸——它没法适配不同角色的展示规则,也不方便单元测试。
常见错误是把脱敏逻辑写死在 SQL 里或模板里,导致后续加个“管理员可看全量”需求时,得翻遍所有 SELECT 语句和 echo 地方。
- 用统一函数封装脱敏规则,比如
mask_phone()、mask_idcard() - 在数据从数据库取出后、返回给前端前这一层处理,不是在 SQL 里拼字符串
- 敏感字段名建议统一约定(如以
_raw结尾),避免混淆原始值和脱敏值
示例:
function mask_phone($phone) {
if (!$phone || !is_string($phone)) return '';
return preg_replace('/^(\d{3})\d{4}(\d{4})$/', '$1****$2', $phone);
}
// 使用:
$user['phone_masked'] = mask_phone($user['phone_raw']);
laravel 模型中自动脱敏输出(Eloquent accessor)
如果你用 Laravel,accessor 是最自然的落点。它让脱敏像访问普通属性一样透明,又不污染原始数据。
立即学习“PHP免费学习笔记(深入)”;
容易踩的坑:在 toArray() 或 API 返回时,accessor 默认不生效,除非显式声明为 $appends;另外,不要在 accessor 里做 DB 查询或耗时操作,会拖慢整个模型序列化。
- 在模型中定义
getPhoneMaskedAttribute()方法 - 把
phone_masked加入$appends数组 - 确保原始字段(如
phone)不在$hidden里,否则 accessor 拿不到值
示例:
class User extends Model
{
protected $appends = ['phone_masked'];
public function getPhoneMaskedAttribute()
{
return mask_phone($this->attributes['phone'] ?? '');
}
}
脱敏规则要区分场景:API 接口 vs 后台导出 Excel
同一个字段,在接口里显示 138****1234,导出 Excel 时可能需要显示 138****1234(仅限内部使用),甚至对审计员显示完整号码。硬编码规则必然崩盘。
关键不是“怎么脱”,而是“谁在什么上下文里决定怎么脱”。建议把脱敏策略抽成可配置对象,按请求来源(request->is('api/*'))、用户角色(auth()->user()->hasRole('auditor'))动态选择规则。
- 避免在控制器里写
if (isAdmin()) { ... } else { ... }判断脱敏方式 - 把脱敏函数变成策略类,例如
PhoneMaskStrategy::forContext($context) - 导出场景通常绕过 Eloquent accessor,需单独调用策略,这点常被忽略
注意 MySQL 8.0+ 的 JSON 字段与 PHP 脱敏协作
如果敏感信息存在 JSON 字段里(比如 profile 是 JSON 类型),PHP 取出来是数组,但直接 json_encode() 后再脱敏,容易漏掉嵌套结构里的 id_card 或 bank_account。
这时候不能只处理顶层字段,得递归遍历。但别手写递归——用 array_walk_recursive() 更稳妥,不过要注意它会跳过关联键名,所以得先判断键名是否匹配敏感词(如含 idcard、phone)。
- 先用
json_decode($row['profile'], true)转成 PHP 数组 - 用自定义函数递归检查键名,对匹配的值调用对应脱敏函数
- 切记:JSON 字段内容不会触发 Eloquent accessor,必须显式处理
示例键名匹配逻辑:
function walk_and_mask(&$data, $keys = ['phone', 'idcard', 'bank']) {
array_walk_recursive($data, function (&$value, $key) use ($keys) {
if (in_array(strtolower($key), $keys)) {
$value = mask_phone($value); // 或根据 key 选不同函数
}
});
}
脱敏不是加星号就完事,真正麻烦的是规则分散、上下文耦合、以及 JSON/嵌套结构这种“看不见的字段”。越早把策略和数据分离,后期改起来越不疼。











