软件编程
位置:首页>> 软件编程>> java编程>> SpringBoot项目中新增脱敏功能的实例代码

SpringBoot项目中新增脱敏功能的实例代码

作者:来瓶小白干Oo  发布时间:2023-11-24 01:32:14 

标签:SpringBoot,脱敏

SpringBoot项目中新增脱敏功能

项目背景

目前正在开发一个SpringBoot项目,此项目有Web端和微信小程序端。web端提供给工作人员使用,微信小程序提供给群众进行预约操作。项目中有部分敏感数据需要脱敏传递给微信小程序,给与群众查看。

项目需求描述

项目中,由于使用端有两个,对于两个端的数据权限并不一样。Web端可以查看所有数据,小程序端只能查看脱敏后的数据。

需要开发一个通用脱敏功能

  • 手动进行脱敏操作

  • 支持多种对象,

  • 支持不同字段,并脱敏指定字段

  • 字段的脱敏方式多样

  • 字段的脱敏方式可自定义

项目解决方案

1. 解决方案

使用注解方式,来支持对指定字段,不同字段,多种脱敏操作,并可以脱离对象。
使用工具对象,通过泛型传参,来支持对不同对象的脱敏操作。

2. 实现代码

2.1 注解 Sensitive

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 自定义数据脱敏
*
* 例如: 身份证,手机号等信息进行模糊处理
*
* @author lzddddd
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {

/**
    * 脱敏数据类型
    */
   SensitiveType type() default SensitiveType.CUSTOMER;

/**
    * 前置不需要打码的长度
    */
   int prefixNoMaskLen() default 0;

/**
    * 后置不需要打码的长度
    */
   int suffixNoMaskLen() default 0;

/**
    * 用什么打码
    */
   String symbol() default "*";

}

2.1 脱敏类型枚举 SensitiveType

public enum SensitiveType {

/**
    * 自定义
    */
   CUSTOMER,
   /**
    * 名称
    **/
   CHINESE_NAME,
   /**
    * 身份证证件号
    **/
   ID_CARD_NUM,
   /**
    * 手机号
    **/
   MOBILE_PHONE,
   /**
    * 固定电话
    */
   FIXED_PHONE,
   /**
    * 密码
    **/
   PASSWORD,
   /**
    * 银行卡号
    */
   BANKCARD,
   /**
    * 邮箱
    */
   EMAIL,
   /**
    * 地址
    */
   ADDRESS,

}

2.3 脱敏工具 DesensitizedUtils

import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.SensitiveType;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;

@Slf4j
public class DesensitizedUtils<T> {

/**
    * 脱敏数据列表
    */
   private List<T> list;

/**
    * 注解列表
    */
   private List<Object[]> fields;

/**
    * 实体对象
    */
   public Class<T> clazz;

public DesensitizedUtils(Class<T> clazz)
   {
       this.clazz = clazz;
   }

/**
    * 初始化数据
    *
    * @param list 需要处理数据
    */
   public void init(List<T> list){
       if (list == null)
       {
           list = new ArrayList<T>();
       }
       this.list = list;

// 得到所有定义字段
       createSensitiveField();
   }

/**
    * 初始化数据
    *
    * @param t 需要处理数据
    */
   public void init(T t){

list = new ArrayList<T>();

if (t != null)
       {
        list.add(t);
       }

// 得到所有定义字段
       createSensitiveField();
   }

/**
    * 得到所有定义字段
    */
   private void createSensitiveField()
   {
       this.fields = new ArrayList<Object[]>();
       List<Field> tempFields = new ArrayList<>();
       tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
       tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
       for (Field field : tempFields)
       {
           // 单注解
           if (field.isAnnotationPresent(Sensitive.class))
           {
               putToField(field, field.getAnnotation(Sensitive.class));
           }

// 多注解
//            if (field.isAnnotationPresent(Excels.class))
//            {
//                Excels attrs = field.getAnnotation(Excels.class);
//                Excel[] excels = attrs.value();
//                for (Excel excel : excels)
//                {
//                    putToField(field, excel);
//                }
//            }
       }
   }

/**
    * 对list数据源将其里面的数据进行脱敏处理
    *
    * @param list
    * @return 结果
    */
   public AjaxResult desensitizedList(List<T> list){

if (list == null){
           return AjaxResult.error("脱敏数据为空");
       }

// 初始化数据
       this.init(list);

int failTimes = 0;

for (T t: this.list) {
           if ((Integer)desensitization(t).get("code") != HttpStatus.SUCCESS){
               failTimes++;
           }
       }

if (failTimes >0){
           return AjaxResult.error("脱敏操作中出现失败",failTimes);
       }

return AjaxResult.success();
   }

/**
    * 放到字段集合中
    */
   private void putToField(Field field, Sensitive attr)
   {
       if (attr != null)
       {
           this.fields.add(new Object[] { field, attr });
       }
   }

/**
    * 脱敏:JavaBean模式脱敏
    *
    * @param t 需要脱敏的对象
    * @return
    */
   public AjaxResult desensitization(T t) {
       if (t == null){
           return AjaxResult.error("脱敏数据为空");
       }

// 初始化数据
       init(t);

try {
           // 遍历处理需要进行 脱敏的字段
           for (Object[] os : fields)
           {
               Field field = (Field) os[0];
               Sensitive sensitive = (Sensitive) os[1];
               // 设置实体类私有属性可访问
               field.setAccessible(true);
               desensitizeField(sensitive,t,field);
           }
           return AjaxResult.success(t);
       } catch (Exception e) {
           e.printStackTrace();
           log.error("日志脱敏处理失败,回滚,详细信息:[{}]", e);
           return AjaxResult.error("脱敏处理失败",e);
       }
   }

/**
    * 对类的属性进行脱敏
    *
    * @param attr 脱敏参数
    * @param vo 脱敏对象
    * @param field 脱敏属性
    * @return
    */
   private void desensitizeField(Sensitive attr, T vo, Field field) throws IllegalAccessException {

if (attr == null || vo == null || field == null){
           return ;
       }

// 读取对象中的属性
       Object value = field.get(vo);
       SensitiveType sensitiveType = attr.type();
       int prefixNoMaskLen = attr.prefixNoMaskLen();
       int suffixNoMaskLen = attr.suffixNoMaskLen();
       String symbol = attr.symbol();

//获取属性后现在默认处理的是String类型,其他类型数据可扩展
       Object val = convertByType(sensitiveType, value, prefixNoMaskLen, suffixNoMaskLen, symbol);
       field.set(vo, val);
   }

/**
    * 以类的属性的get方法方法形式获取值
    *
    * @param o 对象
    * @param name 属性名
    * @return value
    * @throws Exception
    */
   private Object getValue(Object o, String name) throws Exception
   {
       if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name))
       {
           Class<?> clazz = o.getClass();
           Field field = clazz.getDeclaredField(name);
           field.setAccessible(true);
           o = field.get(o);
       }
       return o;
   }

/**
    * 根据不同注解类型处理不同字段
    */
   private Object convertByType(SensitiveType sensitiveType, Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
       switch (sensitiveType) {
           case CUSTOMER:
               value = customer(value, prefixNoMaskLen, suffixNoMaskLen, symbol);
               break;
           case CHINESE_NAME:
               value = chineseName(value, symbol);
               break;
           case ID_CARD_NUM:
               value = idCardNum(value, symbol);
               break;
           case MOBILE_PHONE:
               value = mobilePhone(value, symbol);
               break;
           case FIXED_PHONE:
               value = fixedPhone(value, symbol);
               break;
           case PASSWORD:
               value = password(value, symbol);
               break;
           case BANKCARD:
               value = bankCard(value, symbol);
               break;
           case EMAIL:
               value = email(value, symbol);
               break;
           case ADDRESS:
               value = address(value, symbol);
               break;
       }
       return value;
   }

/*--------------------------下面的脱敏工具类也可以单独对某一个字段进行使用-------------------------*/

/**
    * 【自定义】 根据设置进行配置
    *
    * @param value 需处理数据
    * @param symbol 填充字符
    * @return 脱敏后数据
    */
   public Object customer(Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {

//针对字符串的处理
       if (value instanceof String){
           return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
       }

return value;
   }

/**
    * 对字符串进行脱敏处理
    *
    * @param s 需处理数据
    * @param prefixNoMaskLen 开头展示字符长度
    * @param suffixNoMaskLen 结尾展示字符长度
    * @param symbol 填充字符
    * @return
    */
   private String handleString(String s, int prefixNoMaskLen, int suffixNoMaskLen, String symbol){
       // 是否为空
       if (StringUtils.isBlank(s)) {
           return "";
       }

// 如果设置为空之类使用 * 代替
       if (StringUtils.isBlank(symbol)){
           symbol = "*";
       }

// 对长度进行判断
       int length = s.length();
       if (length > prefixNoMaskLen + suffixNoMaskLen){
           String namePrefix = StringUtils.left(s, prefixNoMaskLen);
           String nameSuffix = StringUtils.right(s, suffixNoMaskLen);
           s = StringUtils.rightPad(namePrefix, StringUtils.length(s) - suffixNoMaskLen, symbol).concat(nameSuffix);
       }

return s;
   }

/**
    * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
    *
    * @param value 需处理数据
    * @param symbol 填充字符
    * @return 脱敏后数据
    */
   public String chineseName(Object value, String symbol) {

//针对字符串的处理
       if (value instanceof String){
           // 对前后长度进行设置 默认 开头只展示一个字符
           int prefixNoMaskLen = 1;
           int suffixNoMaskLen = 0;

return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
       }
       return "";
   }

/**
    * 【身份证号】显示最后四位,其他隐藏。共计18位或者15位,比如:*************1234
    *
    * @param value 需处理数据
    * @param symbol 填充字符
    * @return 脱敏后数据
    */
   public String idCardNum(Object value, String symbol) {

//针对字符串的处理
       if (value instanceof String){
           // 对前后长度进行设置 默认 结尾只展示四个字符
           int prefixNoMaskLen = 0;
           int suffixNoMaskLen = 4;

return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
       }
       return "";
   }

/**
    * 【固定电话】 显示后四位,其他隐藏,比如:*******3241
    *
    * @param value 需处理数据
    * @param symbol 填充字符
    * @return 脱敏后数据
    */
   public String fixedPhone(Object value, String symbol) {

//针对字符串的处理
       if (value instanceof String){
           // 对前后长度进行设置 默认 结尾只展示四个字符
           int prefixNoMaskLen = 0;
           int suffixNoMaskLen = 4;

return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
       }
       return "";

}

/**
    * 【手机号码】前三位,后四位,其他隐藏,比如:135****6810
    *
    * @param value 需处理数据
    * @param symbol 填充字符
    * @return 脱敏后数据
    */
   public String mobilePhone(Object value, String symbol) {

//针对字符串的处理
       if (value instanceof String){
           // 对前后长度进行设置 默认 开头只展示三个字符  结尾只展示四个字符
           int prefixNoMaskLen = 3;
           int suffixNoMaskLen = 4;

return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
       }
       return "";
   }

/**
    * 【地址】只显示到地区,不显示详细地址,比如:湖南省长沙市岳麓区***
    *  只能处理 省市区的数据
    *
    * @param value 需处理数据
    * @param symbol 填充字符
    * @return
    */
   public String address(Object value, String symbol) {

//针对字符串的处理
       if (value instanceof String){
           // 对前后长度进行设置 默认 开头只展示九个字符
           int prefixNoMaskLen = 9;
           int suffixNoMaskLen = 0;

return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
       }
       return "";
   }

/**
    * 【电子邮箱】 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
    *
    * @param value 需处理数据
    * @param symbol 填充字符
    * @return 脱敏后数据
    */
   public String email(Object value, String symbol) {

//针对字符串的处理
       if (value instanceof String){
           // 对前后长度进行设置 默认 开头只展示一个字符  结尾只展示@及后面的地址
           int prefixNoMaskLen = 1;
           int suffixNoMaskLen = 4;

String s = (String) value;
           if (StringUtils.isBlank(s)) {
               return "";
           }

// 获取最后一个@
           int lastIndex = StringUtils.lastIndexOf(s, "@");
           if (lastIndex <= 1) {
               return s;
           } else {
               suffixNoMaskLen = s.length() - lastIndex;
           }

return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
       }
       return "";

}

/**
    * 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:6222600**********1234
    *
    * @param value 需处理数据
    * @param symbol 填充字符
    * @return 脱敏后数据
    */
   public String bankCard(Object value, String symbol) {

//针对字符串的处理
       if (value instanceof String){
           // 对前后长度进行设置 默认 开头只展示六个字符  结尾只展示四个字符
           int prefixNoMaskLen = 6;
           int suffixNoMaskLen = 4;

return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
       }
       return "";

}

/**
    * 【密码】密码的全部字符都用*代替,比如:******
    *
    * @param value 需处理数据
    * @param symbol 填充字符
    * @return
    */
   public String password(Object value,String symbol) {

//针对字符串的处理
       if (value instanceof String){
           // 对前后长度进行设置 默认 开头只展示六个字符  结尾只展示四个字符
           int prefixNoMaskLen = 0;
           int suffixNoMaskLen = 0;

return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
       }
       return "";
   }
}

3 使用实例

3.1 需注解对象

public class User {
   private static final long serialVersionUID = 1L;

/** 普通用户ID */
   private Long userId;

/** 昵称 */
   @Sensitive(type = SensitiveType.CUSTOMER,prefixNoMaskLen = 2,suffixNoMaskLen = 1)
   private String nickName;

/** 姓名 */
   @Sensitive(type = SensitiveType.CHINESE_NAME)
   private String userName;

/** 身份证 */
   @Sensitive(type = SensitiveType.ID_CARD_NUM)
   private String identityCard;

/** 手机号码 */
   @Sensitive(type = SensitiveType.MOBILE_PHONE)
   private String phoneNumber;
}

3.2 脱敏操作


// 脱敏对象
User user = new User();
......
       DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
       desensitizedUtils.desensitization(user);

//脱敏队列
List<User> users = new ArrayList<>();
......
       DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
       desensitizedUtils.desensitizedList(users);

来源:https://blog.csdn.net/qq_36282029/article/details/127886348

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com