溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SpringBoot怎么新增脫敏功能

發(fā)布時間:2022-12-01 10:04:49 來源:億速云 閱讀:152 作者:iii 欄目:開發(fā)技術(shù)

這篇“SpringBoot怎么新增脫敏功能”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“SpringBoot怎么新增脫敏功能”文章吧。

SpringBoot項目中新增脫敏功能

項目背景

目前正在開發(fā)一個SpringBoot項目,此項目有Web端和微信小程序端。web端提供給工作人員使用,微信小程序提供給群眾進行預(yù)約操作。項目中有部分敏感數(shù)據(jù)需要脫敏傳遞給微信小程序,給與群眾查看。

項目需求描述

項目中,由于使用端有兩個,對于兩個端的數(shù)據(jù)權(quán)限并不一樣。Web端可以查看所有數(shù)據(jù),小程序端只能查看脫敏后的數(shù)據(jù)。

需要開發(fā)一個通用脫敏功能

  • 手動進行脫敏操作

  • 支持多種對象,

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

  • 字段的脫敏方式多樣

  • 字段的脫敏方式可自定義

項目解決方案

1. 解決方案

使用注解方式,來支持對指定字段,不同字段,多種脫敏操作,并可以脫離對象。
使用工具對象,通過泛型傳參,來支持對不同對象的脫敏操作。

2. 實現(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ù)脫敏
 *
 * 例如: 身份證,手機號等信息進行模糊處理
 *
 * @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,
    /**
     * 手機號
     **/
    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;

    /**
     * 實體對象
     */
    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ù)進行脫敏處理
     *
     * @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 {
            // 遍歷處理需要進行 脫敏的字段
            for (Object[] os : fields)
            {
                Field field = (Field) os[0];
                Sensitive sensitive = (Sensitive) os[1];
                // 設(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);
        }
    }

    /**
     * 對類的屬性進行脫敏
     *
     * @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ù)可擴展
        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;
    }

    /*--------------------------下面的脫敏工具類也可以單獨對某一個字段進行使用-------------------------*/

    /**
     * 【自定義】 根據(jù)設(shè)置進行配置
     *
     * @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;
    }

    /**
     * 對字符串進行脫敏處理
     *
     * @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 = "*";
        }

        // 對長度進行判斷
        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 需處理數(shù)據(jù)
     * @param symbol 填充字符
     * @return 脫敏后數(shù)據(jù)
     */
    public String chineseName(Object value, String symbol) {

        //針對字符串的處理
        if (value instanceof String){
            // 對前后長度進行設(shè)置 默認(rèn) 開頭只展示一個字符
            int prefixNoMaskLen = 1;
            int suffixNoMaskLen = 0;

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

    /**
     * 【身份證號】顯示最后四位,其他隱藏。共計18位或者15位,比如:*************1234
     *
     * @param value 需處理數(shù)據(jù)
     * @param symbol 填充字符
     * @return 脫敏后數(shù)據(jù)
     */
    public String idCardNum(Object value, String symbol) {

        //針對字符串的處理
        if (value instanceof String){
            // 對前后長度進行設(shè)置 默認(rèn) 結(jié)尾只展示四個字符
            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){
            // 對前后長度進行設(shè)置 默認(rèn) 結(jié)尾只展示四個字符
            int prefixNoMaskLen = 0;
            int suffixNoMaskLen = 4;

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

    }

    /**
     * 【手機號碼】前三位,后四位,其他隱藏,比如:135****6810
     *
     * @param value 需處理數(shù)據(jù)
     * @param symbol 填充字符
     * @return 脫敏后數(shù)據(jù)
     */
    public String mobilePhone(Object value, String symbol) {

        //針對字符串的處理
        if (value instanceof String){
            // 對前后長度進行設(shè)置 默認(rèn) 開頭只展示三個字符  結(jié)尾只展示四個字符
            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){
            // 對前后長度進行設(shè)置 默認(rèn) 開頭只展示九個字符
            int prefixNoMaskLen = 9;
            int suffixNoMaskLen = 0;

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

    /**
     * 【電子郵箱】 郵箱前綴僅顯示第一個字母,前綴其他隱藏,用星號代替,@及后面的地址顯示,比如:d**@126.com
     *
     * @param value 需處理數(shù)據(jù)
     * @param symbol 填充字符
     * @return 脫敏后數(shù)據(jù)
     */
    public String email(Object value, String symbol) {

        //針對字符串的處理
        if (value instanceof String){
            // 對前后長度進行設(shè)置 默認(rèn) 開頭只展示一個字符  結(jié)尾只展示@及后面的地址
            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 需處理數(shù)據(jù)
     * @param symbol 填充字符
     * @return 脫敏后數(shù)據(jù)
     */
    public String bankCard(Object value, String symbol) {

        //針對字符串的處理
        if (value instanceof String){
            // 對前后長度進行設(shè)置 默認(rèn) 開頭只展示六個字符  結(jié)尾只展示四個字符
            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){
            // 對前后長度進行設(shè)置 默認(rèn) 開頭只展示六個字符  結(jié)尾只展示四個字符
            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);

以上就是關(guān)于“SpringBoot怎么新增脫敏功能”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI