1使用场景
你平时肯定做过这样的需求。要求展示用户的手机号,但是不能完全展示,需要在中间给手机号打码,如下图:

我们将关键数据做了适当隐藏,这样就叫数据脱敏。
2数据脱敏
数据脱敏又称数据去隐私化或数据变形,是在给定的规则、策略下对敏感数据进行变换、修改的技术机制,能够在很大程度上解决敏感数据在非可信环境中使用的问题。根据数据保护规范和脱敏策略.对业务数据中的敏感信息实施自动变形.实现对敏感信息的隐藏。
我们来看看具体需求,现在要求对以下数据的电话号码和身份证号码适当隐藏处理:

要求脱敏处理后的效果为:

如上图,我们将手机号与身份证号做了适当的隐藏。

我们常规的做法是先获取到userInfo,然后获取mobile和idCard,再将mobile和idCard做相应的脱敏处理,最后再set进userInfo中。
但这里有个问题这里我们调用了getUserInfo()后,采用了大量的代码去专门处理脱敏数据,而实际上我们只是要获取userInfo的信息而已,为此我决定采用注解的形式,将数据进行数据脱敏即可。
3代码实现
我们先列出数据脱敏的类型

上面为我们需要脱敏的数据枚举。
然后我们根据不同的枚举类型调用各自的脱敏方法

我们还要编写一个处理序列化的类

我们在SensitiveDataSerialize类中重写了serialize(),里面调用了我们之前的jsonHandler(s, jsonGenerator)数据脱敏的方法,这个方法根据不同的枚举类型实现对应的数据脱敏。
然后我们还要重写createContextual()方法,这里面实现的功能就是扫描脱敏注解然后实现各自的数据脱敏。
接下来我们定义脱敏注解。

这个value()是我们传入的类型枚举。
我们来看看具体的脱敏方法,脱敏方法我主要写在这个SensitiveInfoUtils工具类中。这里我们来看一个手机号脱敏方法。

简单来说,就是字符串的截取和替换。
我们在实体类中标上数据脱敏的注解

根据业务需求,我们对手机号和身份证进行数据脱敏。
最后我们在Controller层写一个接口:

启动工程运行接口,获取如开头的结果:

4关于代码封装性思考
前面我们写的jsonHandler()方法是根据类型枚举调用对应的脱敏方法
switch (this.type) {
case CHINESE_NAME: {
jsonGenerator.writeString(SensitiveInfoUtils.chineseName(s));
break;
}
case ID_CARD: {
jsonGenerator.writeString(SensitiveInfoUtils.idCardNum(s));
break;
}
case FIXED_PHONE: {
jsonGenerator.writeString(SensitiveInfoUtils.fixedPhone(s));
break;
}
case MOBILE_PHONE: {
jsonGenerator.writeString(SensitiveInfoUtils.mobilePhone(s));
break;
}
case ADDRESS: {
jsonGenerator.writeString(SensitiveInfoUtils.address(s, 4));
break;
}
case EMAIL: {
jsonGenerator.writeString(SensitiveInfoUtils.email(s));
break;
}
case BANK_CARD: {
jsonGenerator.writeString(SensitiveInfoUtils.bankCard(s));
break;
}
}
但是我们如果要增加脱敏类型,比如增加学校SCHOOL,那么这个方法就要增加
代码语言:javascript复制...
case SCHOOL: {
jsonGenerator.writeString(SensitiveInfoUtils.school(s));
break;
}
如果再增加其他类型,那么这里就要增加其他类型的代码,这个方法就需要修改,这样就破坏了代码的封装性。因此我决定将其改造成策略模式。

我们来看看目录,这里每个类型对应一个策略类,首先我们来看策略接口:
代码语言:javascript复制public interface SensitiveStrategy {
SensitiveType getSensitiveType();
String maskingData(String str);
}
我们定义了两个方法,getSensitiveType()是获取对应枚举类型的方法,maskingData(String str)是实现数据脱敏的方法。因为每个实现的策略类大同小异,这里我们看一个策略类,以MobilePhoneStrategy为例。我们来看看代码:
@Component
public class MobilePhoneStrategy implements SensitiveStrategy {
@Override
public SensitiveType getSensitiveType() {
return SensitiveType.MOBILE_PHONE;
}
@Override
public String maskingData(String str) {
return SensitiveInfoUtils.mobilePhone(str);
}
}
代码中我们返回了对应的手机类型枚举,和调用了对应的手机号脱敏方法。
然后我们写一个service类:
代码语言:javascript复制@Service
public class SensitiveStrategyService {
Map<SensitiveType, SensitiveStrategy> map = Maps.newHashMap();
public SensitiveStrategyService(List<SensitiveStrategy> sensitiveStrategyList) {
sensitiveStrategyList.forEach(sensitiveStrategy -> map.put(sensitiveStrategy.getSensitiveType(), sensitiveStrategy));
}
public String generatorSensitive(SensitiveType typeEnum, String str) {
SensitiveStrategy sensitiveStrategy = map.get(typeEnum);
if (sensitiveStrategy != null) {
return sensitiveStrategy.maskingData(str);
}
return StringUtils.EMPTY;
}
}
这里我们用map存入枚举类型和对应的策略类,map数据如下:

之后我们在serialize()方法中调用:

这我们通过SpringContextHolder.getBean()获取容器中的sensitiveStrategyService实例,然后就是调用方法。
我们同样获取了想要的结果。
使用策略模式,我们需要增加类型时,只需要新增一个策略类,在里面重写好对应的方法,其他地方都不需要修改。
以上就是今天的全部内容了


