SpringBoot項(xiàng)目中新增脫敏功能的實(shí)例代碼
SpringBoot項(xiàng)目中新增脫敏功能
項(xiàng)目背景
目前正在開發(fā)一個(gè)SpringBoot項(xiàng)目,此項(xiàng)目有Web端和微信小程序端。web端提供給工作人員使用,微信小程序提供給群眾進(jìn)行預(yù)約操作。項(xiàng)目中有部分敏感數(shù)據(jù)需要脫敏傳遞給微信小程序,給與群眾查看。
項(xiàng)目需求描述
項(xiàng)目中,由于使用端有兩個(gè),對于兩個(gè)端的數(shù)據(jù)權(quán)限并不一樣。Web端可以查看所有數(shù)據(jù),小程序端只能查看脫敏后的數(shù)據(jù)。
需要開發(fā)一個(gè)通用脫敏功能
- 手動(dòng)進(jìn)行脫敏操作
- 支持多種對象,
- 支持不同字段,并脫敏指定字段
- 字段的脫敏方式多樣
- 字段的脫敏方式可自定義
項(xiàng)目解決方案
1. 解決方案
使用注解方式,來支持對指定字段,不同字段,多種脫敏操作,并可以脫離對象。
使用工具對象,通過泛型傳參,來支持對不同對象的脫敏操作。
2. 實(shí)現(xiàn)代碼
2.1 注解 Sensitive
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定義數(shù)據(jù)脫敏
*
* 例如: 身份證,手機(jī)號等信息進(jìn)行模糊處理
*
* @author lzddddd
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {
/**
* 脫敏數(shù)據(jù)類型
*/
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,
/**
* 手機(jī)號
**/
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> {
/**
* 脫敏數(shù)據(jù)列表
*/
private List<T> list;
/**
* 注解列表
*/
private List<Object[]> fields;
/**
* 實(shí)體對象
*/
public Class<T> clazz;
public DesensitizedUtils(Class<T> clazz)
{
this.clazz = clazz;
}
/**
* 初始化數(shù)據(jù)
*
* @param list 需要處理數(shù)據(jù)
*/
public void init(List<T> list){
if (list == null)
{
list = new ArrayList<T>();
}
this.list = list;
// 得到所有定義字段
createSensitiveField();
}
/**
* 初始化數(shù)據(jù)
*
* @param t 需要處理數(shù)據(jù)
*/
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數(shù)據(jù)源將其里面的數(shù)據(jù)進(jìn)行脫敏處理
*
* @param list
* @return 結(jié)果
*/
public AjaxResult desensitizedList(List<T> list){
if (list == null){
return AjaxResult.error("脫敏數(shù)據(jù)為空");
}
// 初始化數(shù)據(jù)
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("脫敏操作中出現(xiàn)失敗",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("脫敏數(shù)據(jù)為空");
}
// 初始化數(shù)據(jù)
init(t);
try {
// 遍歷處理需要進(jìn)行 脫敏的字段
for (Object[] os : fields)
{
Field field = (Field) os[0];
Sensitive sensitive = (Sensitive) os[1];
// 設(shè)置實(shí)體類私有屬性可訪問
field.setAccessible(true);
desensitizeField(sensitive,t,field);
}
return AjaxResult.success(t);
} catch (Exception e) {
e.printStackTrace();
log.error("日志脫敏處理失敗,回滾,詳細(xì)信息:[{}]", e);
return AjaxResult.error("脫敏處理失敗",e);
}
}
/**
* 對類的屬性進(jìn)行脫敏
*
* @param attr 脫敏參數(shù)
* @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();
//獲取屬性后現(xiàn)在默認(rèn)處理的是String類型,其他類型數(shù)據(jù)可擴(kuò)展
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;
}
/**
* 根據(jù)不同注解類型處理不同字段
*/
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;
}
/*--------------------------下面的脫敏工具類也可以單獨(dú)對某一個(gè)字段進(jìn)行使用-------------------------*/
/**
* 【自定義】 根據(jù)設(shè)置進(jìn)行配置
*
* @param value 需處理數(shù)據(jù)
* @param symbol 填充字符
* @return 脫敏后數(shù)據(jù)
*/
public Object customer(Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
//針對字符串的處理
if (value instanceof String){
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return value;
}
/**
* 對字符串進(jìn)行脫敏處理
*
* @param s 需處理數(shù)據(jù)
* @param prefixNoMaskLen 開頭展示字符長度
* @param suffixNoMaskLen 結(jié)尾展示字符長度
* @param symbol 填充字符
* @return
*/
private String handleString(String s, int prefixNoMaskLen, int suffixNoMaskLen, String symbol){
// 是否為空
if (StringUtils.isBlank(s)) {
return "";
}
// 如果設(shè)置為空之類使用 * 代替
if (StringUtils.isBlank(symbol)){
symbol = "*";
}
// 對長度進(jìn)行判斷
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;
}
/**
* 【中文姓名】只顯示第一個(gè)漢字,其他隱藏為2個(gè)星號,比如:李**
*
* @param value 需處理數(shù)據(jù)
* @param symbol 填充字符
* @return 脫敏后數(shù)據(jù)
*/
public String chineseName(Object value, String symbol) {
//針對字符串的處理
if (value instanceof String){
// 對前后長度進(jìn)行設(shè)置 默認(rèn) 開頭只展示一個(gè)字符
int prefixNoMaskLen = 1;
int suffixNoMaskLen = 0;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【身份證號】顯示最后四位,其他隱藏。共計(jì)18位或者15位,比如:*************1234
*
* @param value 需處理數(shù)據(jù)
* @param symbol 填充字符
* @return 脫敏后數(shù)據(jù)
*/
public String idCardNum(Object value, String symbol) {
//針對字符串的處理
if (value instanceof String){
// 對前后長度進(jìn)行設(shè)置 默認(rèn) 結(jié)尾只展示四個(gè)字符
int prefixNoMaskLen = 0;
int suffixNoMaskLen = 4;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【固定電話】 顯示后四位,其他隱藏,比如:*******3241
*
* @param value 需處理數(shù)據(jù)
* @param symbol 填充字符
* @return 脫敏后數(shù)據(jù)
*/
public String fixedPhone(Object value, String symbol) {
//針對字符串的處理
if (value instanceof String){
// 對前后長度進(jìn)行設(shè)置 默認(rèn) 結(jié)尾只展示四個(gè)字符
int prefixNoMaskLen = 0;
int suffixNoMaskLen = 4;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【手機(jī)號碼】前三位,后四位,其他隱藏,比如:135****6810
*
* @param value 需處理數(shù)據(jù)
* @param symbol 填充字符
* @return 脫敏后數(shù)據(jù)
*/
public String mobilePhone(Object value, String symbol) {
//針對字符串的處理
if (value instanceof String){
// 對前后長度進(jìn)行設(shè)置 默認(rèn) 開頭只展示三個(gè)字符 結(jié)尾只展示四個(gè)字符
int prefixNoMaskLen = 3;
int suffixNoMaskLen = 4;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【地址】只顯示到地區(qū),不顯示詳細(xì)地址,比如:湖南省長沙市岳麓區(qū)***
* 只能處理 省市區(qū)的數(shù)據(jù)
*
* @param value 需處理數(shù)據(jù)
* @param symbol 填充字符
* @return
*/
public String address(Object value, String symbol) {
//針對字符串的處理
if (value instanceof String){
// 對前后長度進(jìn)行設(shè)置 默認(rèn) 開頭只展示九個(gè)字符
int prefixNoMaskLen = 9;
int suffixNoMaskLen = 0;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【電子郵箱】 郵箱前綴僅顯示第一個(gè)字母,前綴其他隱藏,用星號代替,@及后面的地址顯示,比如:d**@126.com
*
* @param value 需處理數(shù)據(jù)
* @param symbol 填充字符
* @return 脫敏后數(shù)據(jù)
*/
public String email(Object value, String symbol) {
//針對字符串的處理
if (value instanceof String){
// 對前后長度進(jìn)行設(shè)置 默認(rèn) 開頭只展示一個(gè)字符 結(jié)尾只展示@及后面的地址
int prefixNoMaskLen = 1;
int suffixNoMaskLen = 4;
String s = (String) value;
if (StringUtils.isBlank(s)) {
return "";
}
// 獲取最后一個(gè)@
int lastIndex = StringUtils.lastIndexOf(s, "@");
if (lastIndex <= 1) {
return s;
} else {
suffixNoMaskLen = s.length() - lastIndex;
}
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【銀行卡號】前六位,后四位,其他用星號隱藏每位1個(gè)星號,比如:6222600**********1234
*
* @param value 需處理數(shù)據(jù)
* @param symbol 填充字符
* @return 脫敏后數(shù)據(jù)
*/
public String bankCard(Object value, String symbol) {
//針對字符串的處理
if (value instanceof String){
// 對前后長度進(jìn)行設(shè)置 默認(rèn) 開頭只展示六個(gè)字符 結(jié)尾只展示四個(gè)字符
int prefixNoMaskLen = 6;
int suffixNoMaskLen = 4;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【密碼】密碼的全部字符都用*代替,比如:******
*
* @param value 需處理數(shù)據(jù)
* @param symbol 填充字符
* @return
*/
public String password(Object value,String symbol) {
//針對字符串的處理
if (value instanceof String){
// 對前后長度進(jìn)行設(shè)置 默認(rèn) 開頭只展示六個(gè)字符 結(jié)尾只展示四個(gè)字符
int prefixNoMaskLen = 0;
int suffixNoMaskLen = 0;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
}
3 使用實(shí)例
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;
/** 手機(jī)號碼 */
@Sensitive(type = SensitiveType.MOBILE_PHONE)
private String phoneNumber;
}3.2 脫敏操作
// 脫敏對象
User user = new User();
......
DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
desensitizedUtils.desensitization(user);
//脫敏隊(duì)列
List<User> users = new ArrayList<>();
......
DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
desensitizedUtils.desensitizedList(users);到此這篇關(guān)于SpringBoot項(xiàng)目中新增脫敏功能的文章就介紹到這了,更多相關(guān)SpringBoot脫敏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java零基礎(chǔ)教程之Windows下安裝 JDK的方法圖解
這篇文章主要介紹了Java零基礎(chǔ)教程之Windows下安裝 JDK的方法圖解,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
Struts1簡介和入門_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Struts1簡介和入門的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09
springboot+maven多環(huán)境動(dòng)態(tài)配置及編譯失敗的解決方案(步驟詳解)
這篇文章主要介紹了springboot+maven多環(huán)境動(dòng)態(tài)配置及編譯失敗的解決方案,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11
Java鏈表中添加元素的原理與實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Java鏈表中添加元素的原理與實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了Java實(shí)現(xiàn)鏈表中添加元素的相關(guān)原理、操作技巧與注意事項(xiàng),需要的朋友可以參考下2020-03-03
聊聊Spring data jpa @query使用原生SQl,需要注意的坑
這篇文章主要介紹了Spring data jpa@query使用原生SQl,需要注意的坑,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08

