本文目录导读:

PHP 实现动态脱敏(数据掩码)有多种方法,以下是几种常用的实现方案:
基础函数封装法
<?php
class DataMasker {
/**
* 手机号脱敏:保留前3后4
*/
public static function maskMobile($mobile) {
if (empty($mobile)) return '';
return substr($mobile, 0, 3) . '****' . substr($mobile, -4);
}
/**
* 身份证号脱敏:保留前6后4
*/
public static function maskIdCard($idCard) {
if (empty($idCard)) return '';
$len = strlen($idCard);
if ($len < 10) return $idCard;
return substr($idCard, 0, 6) . str_repeat('*', $len - 10) . substr($idCard, -4);
}
/**
* 银行卡号脱敏:保留前4后4
*/
public static function maskBankCard($card) {
if (empty($card)) return '';
return substr($card, 0, 4) . '**** **** **** ' . substr($card, -4);
}
/**
* 邮箱脱敏
*/
public static function maskEmail($email) {
if (empty($email)) return '';
list($username, $domain) = explode('@', $email);
$maskedUsername = substr($username, 0, 1) . str_repeat('*', strlen($username) - 1);
return $maskedUsername . '@' . $domain;
}
/**
* 姓名脱敏:保留姓
*/
public static function maskName($name) {
if (empty($name)) return '';
if (mb_strlen($name) == 1) return $name;
return mb_substr($name, 0, 1) . str_repeat('*', mb_strlen($name) - 1);
}
/**
* 通用脱敏方法
*/
public static function mask($data, $type) {
switch ($type) {
case 'mobile': return self::maskMobile($data);
case 'idcard': return self::maskIdCard($data);
case 'bankcard': return self::maskBankCard($data);
case 'email': return self::maskEmail($data);
case 'name': return self::maskName($data);
default: return $data;
}
}
}
配置驱动的脱敏
<?php
class DynamicMasker {
// 脱敏规则配置
private static $rules = [
'user' => [
'mobile' => ['function' => 'maskMobile'],
'id_card' => ['function' => 'maskIdCard'],
'email' => ['function' => 'maskEmail'],
'real_name' => ['function' => 'maskName'],
'address' => ['function' => 'maskCustom', 'params' => ['start' => 3, 'end' => 2]],
],
'order' => [
'order_no' => ['function' => 'maskCustom', 'params' => ['start' => 2, 'end' => 2]],
'customer_name' => ['function' => 'maskName'],
]
];
/**
* 根据场景和字段动态脱敏
*/
public static function maskData(array $data, $scene) {
if (!isset(self::$rules[$scene])) {
return $data;
}
$result = $data;
foreach (self::$rules[$scene] as $field => $rule) {
if (isset($result[$field])) {
$result[$field] = self::{$rule['function']}(
$result[$field],
$rule['params'] ?? []
);
}
}
return $result;
}
// 自定义脱敏:保留开头和结尾指定长度
private static function maskCustom($value, $params = []) {
$start = $params['start'] ?? 0;
$end = $params['end'] ?? 0;
$len = mb_strlen($value);
if ($len <= $start + $end) {
return str_repeat('*', $len);
}
return mb_substr($value, 0, $start) .
str_repeat('*', $len - $start - $end) .
mb_substr($value, -$end);
}
// 其他方法同第一种方案...
}
使用魔术方法动态处理
<?php
class MagicMasker {
private $rawData;
private $maskFields = [
'mobile' => 'maskMobile',
'idcard' => 'maskIdCard',
'bankcard' => 'maskBankCard',
];
public function __construct(array $data) {
$this->rawData = $data;
}
public function __get($name) {
if (isset($this->rawData[$name])) {
// 如果是敏感字段,直接脱敏
if (isset($this->maskFields[$name])) {
$method = $this->maskFields[$name];
return DataMasker::$method($this->rawData[$name]);
}
return $this->rawData[$name];
}
return null;
}
// 获取脱敏后的完整数据
public function toArray() {
$result = [];
foreach ($this->rawData as $key => $value) {
if (isset($this->maskFields[$key])) {
$method = $this->maskFields[$key];
$result[$key] = DataMasker::$method($value);
} else {
$result[$key] = $value;
}
}
return $result;
}
}
// 使用示例
$userData = [
'name' => '张三',
'mobile' => '13812345678',
'idcard' => '110101199001011234',
'email' => 'zhangsan@example.com'
];
$maskedUser = new MagicMasker($userData);
echo $maskedUser->mobile; // 138****5678
支持注解的脱敏(需要反射)
<?php
class AnnotatedMasker {
/**
* @Mask(type="mobile")
*/
public $mobile;
/**
* @Mask(type="idcard")
*/
public $idCard;
public function process($className) {
$reflection = new ReflectionClass($className);
$properties = $reflection->getProperties();
foreach ($properties as $property) {
$docComment = $property->getDocComment();
if (preg_match('/@Mask\(type="(\w+)"\)/', $docComment, $matches)) {
$type = $matches[1];
unserialize($property->getName());
}
}
}
}
使用第三方库(推荐:PHP-Mask)
composer require makinacorpus/php-mask
<?php use MakinaCorpus\PHP-Mask\Masker; $masker = new Masker(); $masked = $masker->mask($data, 'mobile'); // 直接脱敏
动态规则配置示例
<?php
class DynamicConfigMasker {
// 从数据库或配置中心加载规则
private function getRules($scene) {
// 模拟从数据库加载配置
return [
'mobile' => [
'type' => 'replace',
'start' => 3,
'end' => 4,
'char' => '*'
],
'email' => [
'type' => 'regex',
'pattern' => '/^(.*?)(@.*)$/',
'replacement' => '$1***$2'
]
];
}
public function mask(array $data, $scene) {
$rules = $this->getRules($scene);
foreach ($rules as $field => $rule) {
if (!isset($data[$field])) continue;
switch ($rule['type']) {
case 'replace':
$data[$field] = $this->maskByRange(
$data[$field],
$rule['start'],
$rule['end'],
$rule['char'] ?? '*'
);
break;
case 'regex':
$data[$field] = preg_replace(
$rule['pattern'],
$rule['replacement'],
$data[$field]
);
break;
}
}
return $data;
}
}
使用建议
- 根据业务场景选择:如果是简单需求,使用函数封装即可
- 动态配置:如果规则经常变化,建议使用配置驱动
- 性能考虑:大量数据脱敏时,使用数组映射比对象更高效
- 安全性:脱敏操作应该在数据输出前统一处理,避免遗漏
这些方法可以根据你的具体需求灵活选择或组合使用。