⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7348830480789962787 「开大会汪汪队」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

咱又手痒造轮子啦!Hutool工具包有这个一个类DesensitizedUtil实现了一些常见业务数据类型的脱敏,像手机号,中文名,身份证号,银行卡号等。那咱就基于它写一个全局切面,需要脱敏的用注解标识,思路有了说干就干。

咱先定义一个切入点注解@DataDesensitized

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataDesensitized {
}

然后咱再定义一个注解标识字段脱敏@Desensitized

@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Desensitized {
//加上hutool类中的脱敏类型枚举选择脱敏策略
DesensitizedUtil.DesensitizedType type();
}

最后写切面类

@Aspect
@Component
@Slf4j
public class DataDesensitizedAspect {
@AfterReturning(pointcut = "@annotation(dd)", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, DataDesensitized dd, Object result) {
//TODO 这里可以根据组织架构角色控制是否脱敏
boolean need = true;
if (!need) {
return;
}
//方法响应一般有分页实体,list集合,单实体对象,那就分类处理
if (result instanceof PageInfo) {
PageInfo page = (PageInfo) result;
List records = page.getList();
for (Object record : records) {
objReplace(record);
}
} else if (result instanceof List) {
List list = (List) result;
for (Object obj : list) {
objReplace(obj);
}
} else {
objReplace(result);
}
}

public static <T> void objReplace(T t) {
try {
Field[] declaredFields = ReflectUtil.getFields(t.getClass());
for (Field field : declaredFields) {
Desensitized des = field.getAnnotation(Desensitized.class);
//被脱敏注解修饰且string类型
if (des != null &&
"class java.lang.String".equals(field.getGenericType().toString())) {
Object fieldValue = ReflectUtil.getFieldValue(t, field);
if (fieldValue == null || StringUtils.isEmpty(fieldValue.toString())) {
continue;
}
DesensitizedUtil.DesensitizedType type = des.type();
String hide = DesensitizedUtil.desensitized(fieldValue.toString(),type);
ReflectUtil.setFieldValue(t, field, hide);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

在业务方法上标识切入点注解

@Override
@DataProtection
public PageInfo<OrderDetailsVo> queryOrderDetails(QueryParam param) {
return mapper.queryOrderDetails(param);
}

vo实体中需要脱敏的字段加@Desensitized

@Data
public class OrderDetailsVo {
private String orderNo;
private String sn;

@Desensitized(type = DesensitizedUtil.DesensitizedType.CHINESE_NAME)
private String username;

@Desensitized(type = DesensitizedUtil.DesensitizedType.MOBILE_PHONE)
private String mobile;

@Desensitized(type = DesensitizedUtil.DesensitizedType.ID_CARD)
private String idCard;
}

完成!

次日,产品经理要求这个20位的sn字符串从第五位脱敏到第十八位,hutool工具没有这个类型的枚举!成!咱再把轮子改造一下

自己写一个脱敏策略枚举DesensitizedType,对比hutool只加了CUSTOM自定义脱敏类型

public enum DesensitizedType {
//自定义脱敏标识
CUSTOM,
//用户id
USER_ID,
//中文名
CHINESE_NAME,
//身份证号
ID_CARD,
//座机号
FIXED_PHONE,
//手机号
MOBILE_PHONE,
//地址
ADDRESS,
//电子邮件
EMAIL,
//密码
PASSWORD,
//中国大陆车牌,包含普通车辆、新能源车辆
CAR_LICENSE,
//银行卡
BANK_CARD
}

@Desensitized改造

@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Desensitized {
//替换成自己定义的枚举
DesensitizedType type() default DesensitizedType.CUSTOM;

/**
* 当type不指定时,可自定义脱敏起始位置(包含)
*/
int startInclude() default 0;

/**
* 当type不指定时,可自定义脱敏结束位置(不包含) ,-1代表字符串长度
*/
int endExclude() default -1;
}

切面类改造

public static <T> void objReplace(T t) {
try {
Field[] declaredFields = ReflectUtil.getFields(t.getClass());
for (Field field : declaredFields) {
Desensitized des = field.getAnnotation(Desensitized.class);
//被脱敏注解修饰且string类型
if (des != null &&
"class java.lang.String".equals(field.getGenericType().toString())) {
Object fieldValue = ReflectUtil.getFieldValue(t, field);
if (fieldValue == null || StringUtils.isEmpty(fieldValue.toString())) {
continue;
}
String value = fieldValue.toString();
String hide = "";
if (des.type() == DesensitizedType.CUSTOM) {
int startInclude = des.startInclude();
int endExclude = des.endExclude();
if (endExclude == -1) {
endExclude = value.length();
}
hide = StrUtil.hide(value, startInclude, endExclude);
} else {
DesensitizedUtil.DesensitizedType type =
DesensitizedUtil.DesensitizedType.valueOf(des.type().toString());
hide = DesensitizedUtil.desensitized(value, type);
}
ReflectUtil.setFieldValue(t, field, hide);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

实体标识脱敏字段

@Data
public class OrderDetailsVo {
private String orderNo;

@Desensitized(startInclude = 5,endExclude = 18)
private String sn;

@Desensitized(type = DesensitizedType.CHINESE_NAME)
private String username;

@Desensitized(type = DesensitizedType.MOBILE_PHONE)
private String mobile;

@Desensitized(type = DesensitizedType.ID_CARD)
private String idCard;
}

这下可以开开心心把轮子给小伙伴用啦开心!

文章目录