一、简介
在掘金上看到一些关于数据脱敏的文章,也有使用Hutool编写的数据脱敏方案。但在部分处理细节上可能不够完善,因此,我想通过这篇文章分享如何优雅地使用Hutool来实现更加灵活、细致的数据脱敏处理。
二、介绍hutool的脱敏模块
在数据处理或清洗中,可能涉及到很多隐私信息的脱敏工作,因此Hutool针对常用的信息封装了一些脱敏方法。
现阶段支持的脱敏数据类型包括:
- 用户id
- 中文姓名
- 身份证号
- 座机号
- 手机号
- 地址
- 电子邮件
- 密码
- 中国大陆车牌,包含普通车辆、新能源车辆
- 银行卡
三、具体编写代码
提供一个参考的目录结构 :
1. 枚举类定义
在传统的枚举类编写中,我们通常会采用如下的写法:
public enum DesensitizationTypeEnum {
CUSTOMIZE_RULE, // 自定义脱敏
CHINESE_NAME, // 中文名脱敏
ID_CARD, // 身份证脱敏
// ...
}
然而,使用这种传统的枚举写法时,可能会导致在实际使用时不得不通过判断(如 switch-case)的方式来处理具体的逻辑。例如,在进行序列化时,我们可能会写出如下代码:
public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
switch (type) {
case CUSTOMIZE_RULE:
jsonGenerator.writeString(CharSequenceUtil.hide(str, startInclude, endExclude));
break;
case CHINESE_NAME:
jsonGenerator.writeString(DesensitizedUtil.chineseName(String.valueOf(str)));
break;
case ID_CARD:
jsonGenerator.writeString(DesensitizedUtil.idCardNum(String.valueOf(str), 1, 2));
break;
// ...
default:
// 处理默认情况
}
}
这种方式虽然可以实现功能,但存在以下问题:
- 代码冗余:每次添加新类型时都需要修改
switch-case,增加了维护成本。 - 不够优雅:将逻辑与枚举值分离,不符合面向对象编程的设计原则。
为了优化这一点,我们可以通过改造枚举类,将具体的逻辑直接与枚举值关联。如下所示:
@AllArgsConstructor
@Getter
public enum DesensitizationTypeEnum {
/**
* 自定义
*/
CUSTOMIZE_RULE(str-> str),
/**
* 中文名
*/
CHINESE_NAME(str->DesensitizedUtil.chineseName(String.valueOf(str))),
/**
* 身份证号
*/
ID_CARD(str->DesensitizedUtil.idCardNum(String.valueOf(str), 1, 2)),
/**
* 座机号
*/
FIXED_PHONE(str->DesensitizedUtil.fixedPhone(String.valueOf(str))),
/**
* 手机号
*/
MOBILE_PHONE(str->DesensitizedUtil.fixedPhone(String.valueOf(str))),
/**
* 地址
*/
ADDRESS(str-> DesensitizedUtil.address(String.valueOf(str),8)),
/**
* 电子邮件
*/
EMAIL(str->DesensitizedUtil.email(String.valueOf(str))),
/**
* 密码
*/
PASSWORD(str->DesensitizedUtil.password(String.valueOf(str))),
/**
* 中国大陆车牌,包含普通车辆、新能源车辆
*/
CAR_LICENSE(str->DesensitizedUtil.carLicense(String.valueOf(str))),
/**
* 银行卡
*/
BANK_CARD(str->DesensitizedUtil.bankCard(String.valueOf(str)));
private final Function<String,String> serialize;
}
通过这种方式:
- 逻辑与枚举结合:每个枚举值都直接包含了相应的处理逻辑,避免了冗长的
switch-case。 - 扩展性强:新增类型只需添加新的枚举项,而无需修改其他代码。
- 代码简洁:代码更简洁,易读性更好,符合开闭原则(Open-Closed Principle)。
在数据脱敏的实际应用中,大多数脱敏操作只需要简单地处理字符串,而无需太多的入参。例如,处理身份证号、手机号、邮箱等时,只需使用预定义的规则即可。但对于某些特殊情况,如自定义脱敏(CUSTOMIZE_RULE),可能需要指定更细粒度的脱敏方式(如指定位置1-2脱敏),这种情况很好,我们就在 自定义Jackson中特殊实现。
2. 定义 Desensitization 注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerialize.class)
public @interface Desensitization {
/**
* 脱敏数据类型,在MY_RULE的时候,startInclude和endExclude生效
*/
DesensitizationTypeEnum type();
/**
* 脱敏开始位置(包含)
*/
int startInclude() default 0;
/**
* 脱敏结束位置(不包含)
*/
int endExclude() default 0;
}
3. 自定义Jackson序列化类
@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {
private DesensitizationTypeEnum type;
private Integer startInclude;
private Integer endExclude;
@Override
public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
// 展示给管理员的数据不脱敏
if (SecurityUtil.isAdmin()) {
jsonGenerator.writeString(str);
return;
}
// 不为管理员则数据脱敏
if (type.equals(DesensitizationTypeEnum.CUSTOMIZE_RULE)) {
jsonGenerator.writeString(CharSequenceUtil.hide(type.getSerialize().apply(str), startInclude, endExclude));
} else {
jsonGenerator.writeString(type.getSerialize().apply(str));
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
// 检查数据类型是否为String类型
if (beanProperty.getType().getRawClass() == String.class) {
// 获取定义的注解
Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
if (desensitization != null) {
// 如果注解不为null,则创建并返回DesensitizationSerialize实例
return new DesensitizationSerialize(desensitization.type(),
desensitization.startInclude(), desensitization.endExclude());
}
}
// 返回默认的值序列化器
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
// 如果BeanProperty为null,则返回空值序列化器
return serializerProvider.findNullValueSerializer(null);
}
}
4. 实际使用
4.1 数据脱敏测试
实体类中进行手机号、邮箱脱敏
@Schema(description ="当前登录用户视图对象")
@Data
public class UserInfoVO {
@Schema(description = "手机号")
@Desensitization(type = DesensitizationTypeEnum.MOBILE_PHONE)
private String phoneNumber;
@Schema(description = "邮箱")
@Desensitization(type = DesensitizationTypeEnum.EMAIL)
private String email;
// ... 其他字段
}
4.2 数据展示
{
email: "3********@qq.com",
phonenumber: "1822*****45",
...
}
代码比较简单 : 源码