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


猜你喜欢
- 本文介绍的仿IOS对话框的实现,先来看一下效果图具体代码如下:public class AlertDialog { private Cont
- 本文实例展示了C#自定义函数NetxtString实现生成随机字符串的方法,在进行C#项目开发中非常实用!分享给大家供大家参考。一、生成随机
- 零、关于HibernateHibernate是冬眠的意思,它是指动物的冬眠,但是本文讨论的Hibernate却与冬眠毫无关系,而是接下来要讨
- /** * 冒泡排序估计是每本算法书籍都会提到的排序方法。 * 它的基本思路是对长度为N的序列,用N趟来将其排成有序序列。 * 第1趟
- 在android开发中,一说起线程的使用,很多人马上想到new Thread(){...}.start()这种方式。这样使用当然可以,但是多
- 这篇文章主要介绍了Java类加载器ClassLoader用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值
- Android CheckBox中设置padding无效解决办法CheckBox使用本地图片资源CheckBox是Android中用的比较多
- 目录面试题1:如何判断对象是否存活1.引用计数算法2.可达性分析算法面试题2:哪些对象可以作为GC Roots?面试题3:你了解的对象引用方
- selenium 中如何处理弹出窗口阅读目录原理测试页面的HTMLJava 代码原理在代码里, 通过 &n
- 通过之前三篇关于Spring Boot异步任务实现的博文,我们分别学会了用@Async创建异步任务、为异步任务配置线程池、使用多个线程池隔离
- 本博文将为您提供自Java 7以来增加的很棒的新功能的示例。我将展示每个Java版本的至少一项重大改进,一直到2020年秋季发布的Java
- c# label的内容显示不全,需要设置如下属性即可:1、将Lable的font属性的字体改成宋体;2、将AutoSize属性改成true;
- 以前在公司做项目的时候,遇到了分辨率的适配问题,说起来当时挺纠结的,因为没有外网,所以这个问题,都是黑暗中摸索的,尝试了许多方法,最后和徒弟
- 在 Eclipse 里新建好工程后,默认会有一个assets目录,在 Eclipse 中直接将准备好的 SQLite 数据库复制到该目录中,
- 前言该文章为对工作中部分业务实现的总结,阅读时间:20分钟,版本:Android 6.0 - 9.0 update time 2021年02
- 前言在上一篇普通的加载千篇一律,有趣的 loading 万里挑一 中,我们介绍了使用Path类的PathMetrics属性来控制绘制点在路径
- yml文件参数的读取附上一个较为常见的application.yml文件示例server: port: 9999 u
- 在 Java 语言中,运算符有算数运算符、关系运算符、逻辑运算符、赋值运算符、字符串连接运算符、条件运算符。算数运算符算数运算符是我们最常用
- CSRF介绍CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click atta
- 本文实例讲述了Android开发使用Messenger及Handler进行通信的方法。分享给大家供大家参考,具体如下:1. 客户端servi