如何在Think中对敏感数据脱敏 获取器动态替换手机号中间星号加密完全指南|Duuu笔记
在ThinkPHP模型获取器中用getMobileAttr方法结合substr_replace实现手机号脱敏,需判空防错,脱敏后所有输出路径均生效,关联模型需单独定义。
ThinkPHP 获取器里怎么写手机号脱敏逻辑
直接在模型的获取器(accessor)里做字符串替换,别在控制器或视图里临时处理。ThinkPHP 会自动在读取字段时触发获取器,适合统一脱敏。
关键点是:获取器函数名必须是
get{Field}Attr
格式,且参数固定为
$value
;脱敏逻辑要判断值是否为空或非字符串,避免报错。
手机号字段假设叫
mobile
,对应获取器函数名就是
getMobileAttr
用
substr_replace()
比正则更轻量,也更可控
必须加空值判断,否则查不到数据时
$value
是
null
,
substr_replace(null, ...)
会警告
public function getMobileAttr($value)
{
if (empty($value) || !is_string($value)) {
return $value;
}
return substr_replace($value, '****', 3, 4);
}
为什么不用正则或自定义函数封装
正则在简单脱敏场景下属于杀鸡用牛刀——
substr_replace()
更快、无 PCRE 依赖、不触发额外编译开销。ThinkPHP 6+ 默认关闭了正则缓存,每次调用都重新解析,对高频列表页有感知延迟。
也不建议抽成独立工具函数再调用,因为获取器本身是“透明增强”,一旦引入外部依赖,就可能破坏模型序列化、JSON 输出等默认行为。
“
PHP免费学习笔记(深入)
”;
substr_replace($mobile, '****', 3, 4)
直接从第 4 位(索引 3)开始替换成 4 个星号,适配 11 位手机号
如果数据库存的是带区号或国际码(如 +8613812345678),先用
preg_replace('/[^0-9]/', '', $value)
清洗再截取
别用
str_replace()
,它无法按位置替换,容易误伤其他数字字段
获取器脱敏后导出 Excel 或 API 返回时还显示星号吗
会。获取器作用于模型属性读取全过程,包括
toArray()
、
json_encode()
、
export()
等所有输出路径。这是它的优势,也是陷阱。
如果导出原始数据需要明文手机号,得临时禁用获取器:
$user->getAttr('mobile', null, false)
(TP6.1+)
TP6.0 及以下不支持跳过获取器,只能用
$user->getData('mobile')
绕过
API 接口若需同时返回脱敏版和明文版,不要新增字段,改用
append
添加一个
mobile_raw
属性,显式控制
脱敏后搜索还能搜到原手机号吗
不能。获取器只影响读取,不影响查询条件。你在
where('mobile', '138****5678')
是搜不到的——数据库存的是完整号码,而你传入的是脱敏串。
搜索必须用原始值。常见错误是前端把脱敏后的
138****5678
发给后端当查询条件,后端没校验直接拼进 where,结果永远查不到。
搜索接口应约定只接收原始手机号,或加个
search_mobile
字段专用于模糊查
如果必须支持“输脱敏号也能搜”,得在搜索前反推:比如把
138****5678
替换成
138%5678
,再用
like
查询,但注意性能和准确性
别在获取器里做
LIKE
匹配,那是查询层的事,不是展示层的职责
最常被忽略的一点:获取器不会自动生效于关联模型的字段。比如
User::with('profile')->find()
,如果
profile.mobile
也要脱敏,得在
Profile
模型里单独定义
getMobileAttr
—— 这个得一个个补,没有继承或全局配置。
