国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【數(shù)據(jù)脫敏方案】不使用 AOP + 注解,使用 SpringBoot+YAML 實現(xiàn)

這篇具有很好參考價值的文章主要介紹了【數(shù)據(jù)脫敏方案】不使用 AOP + 注解,使用 SpringBoot+YAML 實現(xiàn)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

引入

在項目中遇到一個需求,需要對交易接口返回結(jié)果中的指定字段進行脫敏操作,但又不能使用AOP+注解的形式,于是決定使用一種比較笨的方法:

  1. 首先將所有需要脫敏字段及其對應(yīng)脫敏規(guī)則存儲到 Map 中。
  2. 在接口返回時,遍歷結(jié)果中的所有字段,判斷字段名在 Map 中是否存在:
    • 如果不存在:說明該字段不需要脫敏,不做處理即可。
    • 如果存在:說明該字段需要脫敏,從 Map 中獲取對應(yīng)的脫敏規(guī)則進行脫敏。
  3. 最后返回脫敏之后的結(jié)果。

認(rèn)識 YAML 格式規(guī)范

由于返回的結(jié)果涉及到嵌套 Map,所以決定采用 YAML 格式的文件存儲脫敏規(guī)則,那么為了大家統(tǒng)一維護和開發(fā),就需要大家對 YAML 格式進行了解,遵守規(guī)范,不易出錯,少走彎路。

YAML(YAML Ain’t Markup Language)與傳統(tǒng)的 JSON、XML 和 Properties 文件一樣,都是用于數(shù)據(jù)序列化的格式,常用于配置文件和數(shù)據(jù)傳輸。

相比于其他格式,YAML 是一種輕量級的數(shù)據(jù)序列化格式,它的設(shè)計初衷是為了簡化復(fù)雜性,提高人類可讀性,并且易于實現(xiàn)和解析。

  • 與 JSON 相比:YAML 在語法上更為靈活,允許使用更簡潔的方式來表示數(shù)據(jù)結(jié)構(gòu)。

  • 與 XML 相比:YAML 的語法更為簡潔,沒有繁瑣的標(biāo)簽和尖括號。

  • 與 Properties 相比:YAML 支持更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),包括嵌套的鍵值對和列表。

除此之外,YAML 還支持跨平臺、跨語言,可以被多種編程語言解析,這使得YAML非常適合用于不同語言之間的數(shù)據(jù)傳輸和交換。

YAML 文件的語法非常簡潔明了,以下是它的語法規(guī)范:

  1. 基本語法:

    • 使用 縮進表示層級關(guān)系,可以使用空格或制表符進行縮進,但不能混用。
    • 使用冒號(:)表示鍵值對,鍵值對之間使用換行分隔。
    • 使用破折號(-)表示列表項,列表項之間也使用換行分隔。
    # 使用縮進表示層級關(guān)系
    server:
      port: 8080
    
    # 使用冒號表示鍵值對
    name: John Smith
    age: 30
    
    # 使用破折號表示列表項
    hobbies:
      - reading
      - hiking
      - swimming
    
  2. 注釋:

    • 使用井號(#)表示注釋,在 # 后面的內(nèi)容被視為注釋,可以出現(xiàn)在行首或行尾。
    # 這是一個注釋
    name: John Smith
    age: 30 # 這也是一個注釋
    
  3. 字符串:

    • 字符串可以使用單引號或雙引號括起來,也可以不使用引號。
    • 使用雙引號時,可以使用轉(zhuǎn)義字符(如 \n 表示換行)和轉(zhuǎn)義序列(如 \u 表示 Unicode 字符)。
    # 使用雙引號表示字符串
    name: "John Smith"
    
    # 使用單引號表示字符串
    nickname: 'Johnny'
    
  4. 鍵值對:

    • 鍵值對使用冒號(:)表示,鍵和值之間使用一個 空格 分隔。
    • 鍵可以是字符串或純量(如整數(shù)、布爾值等)。
    • 值可以是字符串、純量、列表或嵌套的鍵值對。
    # 鍵和值之間使用一個空格分隔
    name: John Smith
    
    # 鍵可以是字符串或純量
    age: 30
    
    # 值可以是字符串、純量、列表或嵌套的鍵值對
    address:
      city: San Francisco
      state: California
      zip: 94107
    
  5. 列表:

    • 使用破折號(-)表示列表項。
    • 列表項可以是字符串、純量或嵌套的列表或鍵值對。
    # 使用破折號表示列表項
    hobbies:
      - reading
      - hiking
      - swimming
      
    # 列表項可以是字符串、純量或嵌套的列表或鍵值對
    people:
      - name: John Smith
        age: 30
      - name: Jane Doe
        age: 25
    
  6. 引用:

    • 使用&表示引用,使用*表示引用的內(nèi)容。
    # 使用&表示引用
    address: &myaddress
      city: San Francisco
      state: California
      zip: 94107
      
    # 使用*表示引用的內(nèi)容
    shippingAddress: *myaddress
    
  7. 多行文本塊:

    • 使用|保留換行符,保留文本塊的精確格式。
    • 使用>折疊換行符,將文本塊折疊成一行,并根據(jù)內(nèi)容自動換行。
    # 使用|保留換行符
    description: |
      This is a
      multi-line
      string.
      
    # 使用>折疊換行符
    summary: >
      This is a summary
      that may contain
      line breaks.
    
  8. 數(shù)據(jù)類型:

    • YAML支持多種數(shù)據(jù)類型,包括字符串、整數(shù)、浮點數(shù)、布爾值、日期和時間等。
    • 可以使用標(biāo)記來表示一些特殊的數(shù)據(jù)類型,如 !!str 表示字符串類型、!!int 表示整數(shù)類型等。
    # 使用標(biāo)記表示數(shù)據(jù)類型
    age: !!int 30
    weight: !!float 65.5
    isMale: !!bool true
    created: !!timestamp '2022-01-01 12:00:00'
    
  9. 多文件:

    • 可以使用—表示多個 YAML 文件之間的分隔符。每個文件可以使用任何 YAML 語法。
    # 第一個YAML文件
    name: John Smith
    age: 30
    
    ---
    
    # 第二個YAML文件
    hobbies:
      - reading
      - hiking
      - swimming
    

定義脫敏規(guī)則格式

對于數(shù)據(jù)結(jié)構(gòu)簡單的接口返回結(jié)果,脫敏規(guī)則格式定義為【交易號->字段->規(guī)則】:

交易號:
  字段名:
    規(guī)則: '/^(1[3-9][0-9])\d{4}(\d{4}$)/'

同時接口返回的結(jié)果中可能用有嵌套列表,那么針對這種復(fù)雜的結(jié)構(gòu)就定義格式為【交易號->字段(列表)->字段->規(guī)則】,即:

交易號:
  字段名(列表):
    字段名:
      規(guī)則: '/^(1[3-9][0-9])\d{4}(\d{4}$)/'

使用這種層級結(jié)構(gòu),我們完全可以通過 Map.get("Key") 的形式獲取到指定交易,指定字段的脫敏規(guī)則。

脫敏邏輯實現(xiàn)

讀取 YAML 配置文件獲取脫敏規(guī)則

  1. 首先創(chuàng)建 YAML 文件 desensitize.yml 添加對應(yīng)交易字段的脫敏規(guī)則:

    Y3800:
      phone:
        rule: "(\\d{3})\\d{4}(\\d{4})"
        format: "$1****$2"
      idCard:
        rule: "(?<=\\w{6})\\w(?=\\w{4})"
        format: "*"
    Y3801:
      idCard:
        rule: "(?<=\\w{3})\\w(?=\\w{4})"
        format: "+"
      list:
        phone:
          rule: "(\\d{3})\\d{4}(\\d{4})"
          format: "$1++++$2"
    
  2. 定義脫敏工具類 DataDesensitizationUtils 編寫我們的脫敏邏輯:

    public class DataDesensitizationUtils {
    }
    
  3. DataDesensitizationUtils 工具類中,我們需要實現(xiàn)在項目啟動時,讀取 desensitize.yml 文件中的內(nèi)容,并轉(zhuǎn)為我們想要的 Map 鍵值對數(shù)據(jù)類型:

    /**
     * 讀取yaml文件內(nèi)容并轉(zhuǎn)為Map
     * @param yamlFile yaml文件路徑
     * @return Map對象
     */
    public static Map<String, Object> loadYaml(String yamlFile) {
        Yaml yaml = new Yaml();
        try (InputStream in = DataDesensitizationUtils.class.getResourceAsStream(yamlFile)) {
            return yaml.loadAs(in, Map.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    

    在上述代碼中,我們通過 getResourceAsStream 方法根據(jù)指定的 YAML 文件的路徑從類路徑中獲取資源文件的輸入流。

    然后使用 loadAs 方法將輸入流中的內(nèi)容按照 YAML 格式進行解析,并將解析結(jié)果轉(zhuǎn)換為指定的 Map.class 類型。

    最后使用 try-with-resources 語句來自動關(guān)閉輸入流。

通過鍵路徑獲取對應(yīng)字段規(guī)則

原始
  1. 在上文中我們已經(jīng)將 desensitize.yml 文件中所有的脫敏規(guī)則都以 key-Value 的形式存儲到了 Map 中,因此我們只需要通過 Key 從 Map 中獲取即可。接下來編寫方法通過 Key 獲取指定字段對應(yīng)脫敏規(guī)則:

    public static void main(String[] args) {
        // 加載 YAML 文件并獲取頂層的 Map 對象,路徑基于 resources 目錄
        Map<String, Object> yamlMap = loadYaml("/desensitize.yml");
        System.out.println(yamlMap);
    
        // 從頂層的 Map 中獲取名為 "Y3800" 的嵌套 Map
        Map<String, Object> Y3800= (Map<String, Object>) yamlMap.get("Y3800");
        System.out.println(Y3800);
    
        // 從 "Y3800" 的嵌套 Map 中獲取名為 "phone" 的嵌套 Map
        Map<String, Object> phone = (Map<String, Object>) Y3800.get("phone");
        System.out.println(phone);
    }
    

    輸出結(jié)果如下:

    {Y3800={phone={rule=(\d{3})\d{4}(\d{4}), format=$1****$2}, idCard={rule=(?<=\w{3})\w(?=\w{4}), format=*}}, Y3801={name={rule=.(?=.), format=+}, idCard={rule=(?<=\w{3})\w(?=\w{4}), format=+}, list={card={rule=\d(?=\d{4}), format=+}}}}
    {phone={rule=(\d{3})\d{4}(\d{4}), format=$1****$2}, idCard={rule=(?<=\w{3})\w(?=\w{4}), format=*}}
    {rule=(\d{3})\d{4}(\d{4}), format=$1****$2}
    

    轉(zhuǎn)為 JSON 格式顯示如下:

    • 輸出 YAML 文件中的全部數(shù)據(jù):

      {
        "Y3800": {
          "phone": {
            "rule": "(\\d{3})\\d{4}(\\d{4})",
            "format": "$1****$2"
          },
          "idCard": {
            "rule": "(?<=\\w{3})\\w(?=\\w{4})",
            "format": "*"
          }
        },
        "Y3801": {
          "name": {
            "rule": ".(?=.)",
            "format": "+"
          },
          "idCard": {
            "rule": "(?<=\\w{3})\\w(?=\\w{4})",
            "format": "+"
          },
          "list": {
            "card": {
              "rule": "\\d(?=\\d{4})",
              "format": "+"
            }
          }
        }
      }
      
    • 輸出 Y3800 層級下的數(shù)據(jù):

      {
        "phone": {
          "rule": "(\\d{3})\\d{4}(\\d{4})",
          "format": "$1****$2"
        },
        "idCard": {
          "rule": "(?<=\\w{3})\\w(?=\\w{4})",
          "format": "*"
        }
      }
      
    • 輸出 phone 層級下的數(shù)據(jù):

      {
        "rule": "(\\d{3})\\d{4}(\\d{4})",
        "format": "$1****$2"
      }
      

在這里,我們需要仔細思考一下,在我們通過 Key 獲取指定層級下的數(shù)據(jù)時,我們需要不斷的調(diào)用 Map.get("Key") 方法,即結(jié)構(gòu)每嵌套一次,就需要一次 getKey,那么這里是否有優(yōu)化的方法呢?

答案是:有的,因為有問題就會有答案。

優(yōu)化后

首先我們需要先了解一個概念:

Y3800:
  phone:
    rule: "(\\d{3})\\d{4}(\\d{4})"
    format: "$1****$2"

當(dāng)我們要從上述數(shù)據(jù)中獲取 phone 的脫敏規(guī)則時,我們需要先從 Map 中 get("Y3800") 獲取 Y3800 下的數(shù)據(jù),再通過 get("phone") 獲取 phone 下的規(guī)則,那么 Y3800->phone 就是 phone 的鍵路徑。

基于此,我們可以實現(xiàn)這樣一個方法,我們直接給出指定字段的鍵路徑,在方法中通過遞歸的方式從 Map 中獲取到該鍵路徑下的所有數(shù)據(jù),然后返回即可。

即優(yōu)化思路為:通過遞歸和判斷來遍歷嵌套的 Map,直到找到鍵路徑所對應(yīng)的最里層的嵌套 Map,并返回該 Map 對象。

優(yōu)化后方法如下:

/**
 * 遞歸獲取嵌套 Map 數(shù)據(jù)
 *
 * @param map  嵌套數(shù)據(jù)源的 Map
 * @param keys 嵌套鍵路徑
 * @return 嵌套數(shù)據(jù)對應(yīng)的 Map
 */
@SuppressWarnings("unchecked")
public static Map<String, Object> getNestedMapValues(Map<String, Object> map, String... keys) {
    // 如果鍵路徑為空或者第一個鍵不在 Map 中,則返回 null
    if (keys.length == 0 || !map.containsKey(keys[0])) {
        return null;
    }

    // 獲取第一個鍵對應(yīng)的嵌套對象
    Object nestedObject = map.get(keys[0]);

    // 如果鍵路徑長度為 1,說明已經(jīng)到達最里層的嵌套 Map,直接返回該 Map 對象
    if (keys.length == 1) {
        if (nestedObject instanceof Map) {
            return (Map<String, Object>) nestedObject;
        } else {
            return null;
        }
    } else {
        // 如果嵌套對象是 Map,繼續(xù)遞歸查找下一個鍵的嵌套 Map
        if (nestedObject instanceof Map) {
            return getNestedMapValues((Map<String, Object>) nestedObject, Arrays.copyOfRange(keys, 1, keys.length));
        } else {
            // 嵌套對象既不是 Map 也不是 List,返回 null
            return null;
        }
    }
}

調(diào)用方法時傳入 Key 的嵌套路徑即可:

public static void main(String[] args) {
    // 加載 YAML 文件并獲取頂層的 Map 對象
    Map<String, Object> yamlMap = loadYaml("/desensitize.yml");
    System.out.println(yamlMap);

    // 獲取 Y3800 -> phone 下的數(shù)據(jù)轉(zhuǎn)為 Map
    Map<String, Object> y3800PhoneMap = YamlUtils.getNestedMap(yamlMap, "Y3800", "phone");
    System.out.println("Y3800 -> phone : " + y3800NameMap);
}

具體來說,主要分為以下幾步:

  1. 首先判斷鍵路徑是否為空或者第一個鍵是否在 Map 中。如果鍵路徑為空或者第一個鍵不在 Map 中,則返回 null。
  2. 獲取第一個鍵對應(yīng)的嵌套對象。通過 get 方法獲取第一個鍵對應(yīng)的嵌套對象。
  3. 判斷是否到達最里層的嵌套 Map。如果鍵路徑長度為 1,說明已經(jīng)到達最里層的嵌套 Map,直接返回該 Map 對象。
  4. 繼續(xù)遞歸查找下一個鍵的嵌套 Map。如果嵌套對象是 Map,則繼續(xù)遞歸查找下一個鍵的嵌套 Map。
  5. 返回結(jié)果。返回遞歸查找的結(jié)果。

對數(shù)據(jù)進行脫敏處理

獲取到字段的脫敏規(guī)則后,我們就可以編寫方法實現(xiàn)對源數(shù)據(jù)做脫敏處理,脫敏方法如下:

/**
 * 使用指定規(guī)則對數(shù)據(jù)進行脫敏處理
 *
 * @param data 要進行脫敏處理的數(shù)據(jù)
 * @param map 包含脫敏規(guī)則和格式的參數(shù)映射
 *            - "rule" 表示脫敏規(guī)則的正則表達式
 *            - "format" 表示替換脫敏部分的字符串,默認(rèn)為 "*"
 * @return 脫敏后的數(shù)據(jù)
 */
private static String desensitizeLogic(String data, Map<String, Object> map) {
    if (map.containsKey("rule")) {
        String rule = (String) map.get("rule");
        String sign = "*";
        if (map.containsKey("format")) {
            sign = (String) map.get("format");
        }
        return data.replaceAll(rule, sign);
    }
    return data;
}

遞歸生成字段對應(yīng)的鍵路徑

目前我們已經(jīng)實現(xiàn)了通過字段的鍵路徑獲取到該字段對應(yīng)規(guī)則的方法 getNestedMapValues(),那么接下來我們只需要生成字段對應(yīng)的鍵路徑,然后調(diào)用方法 getNestedMapValues() 獲取到脫敏規(guī)則后調(diào)用 desensitizeLogic() 對源數(shù)據(jù)進行脫敏即可。

提供源數(shù)據(jù)格式如下:

{
  "txEntity": {
    "idCard": "130428197001180384",
    "name": "趙士杰",
    "list": [
      {
        "phone": "17631007015"
      },
      {
        "phone": "17631007015"
      }
    ]
  },
  "txHeader": {
    "servNo": "Y3801"
  }
}

根據(jù)上述數(shù)據(jù)結(jié)構(gòu),首先我們需要從 txHeader 中獲取 servNo,之后遞歸遍歷 txEntity 中的元素即可。

具體方法如下:

/**
 * 對指定實體數(shù)據(jù)進行脫敏處理
 *
 * @param entity 要進行脫敏處理的實體數(shù)據(jù)
 * @param servNo 當(dāng)前交易的服務(wù)號,用于記錄日志
 * @param path 當(dāng)前實體數(shù)據(jù)在整個數(shù)據(jù)結(jié)構(gòu)中的路徑,用于記錄日志
 */
public static void parseData(Object entity, String servNo, String path) {
    if (entity instanceof Map) {
        for (Map.Entry<String, Object> entry : ((Map<String, Object>) entity).entrySet()) {
            // 計算當(dāng)前鍵值對在整個數(shù)據(jù)結(jié)構(gòu)中的路徑
            String currentPath = path.isEmpty() ? entry.getKey() : path + "," + entry.getKey();
            if (entry.getValue() instanceof Map) {
                // 如果當(dāng)前值是 Map 類型,則遞歸處理子節(jié)點
                parseData(entry.getValue(), servNo, currentPath);
            } else if (entry.getValue() instanceof List) {
                // 如果當(dāng)前值是 List 類型,則遍歷列表中的每個元素并遞歸處理子節(jié)點
                for (Object item : (List) entry.getValue()) {
                    if (item instanceof Map) {
                        parseData(item, servNo, currentPath);
                    }
                }
            } else {
                // 如果當(dāng)前值不是 Map 或 List,則進行脫敏處理
                String p = servNo + "," +currentPath;
                String[] keyPaths = p.split(",");

                // 獲取當(dāng)前節(jié)點的脫敏規(guī)則和格式
                Map<String, Object> nestedMap = getNestedMap(keyPaths);

                if(Objects.nonNull(nestedMap)){
                    // 記錄日志
                    log.info("-----------------交易【{}】,字段【{}】開始脫敏-----------------",servNo,currentPath.replace(",","->"));
                    log.info("原始值:【{}:{}】",entry.getKey(),entry.getValue());
                    log.info("脫敏規(guī)則:{}",nestedMap);
                    // 對當(dāng)前節(jié)點的值進行脫敏處理
                    String desensitized = desensitizeLogic((String) entry.getValue(), nestedMap);
                    entry.setValue(desensitized);
                    // 記錄日志
                    log.info("脫敏值:【{}:{}】",entry.getKey(),entry.getValue());
                    log.info("-----------------交易【{}】,字段【{}】脫敏結(jié)束-----------------",servNo,currentPath.replace(",","->"));
                }
            }
        }
    }
}

該方法接收一個實體數(shù)據(jù) entity,一個服務(wù)號 servNo 和一個路徑 path 作為參數(shù)。在方法體內(nèi),會遍歷實體數(shù)據(jù)的鍵值對,并根據(jù)具體情況遞歸處理子節(jié)點或進行脫敏處理。

  • 當(dāng)實體數(shù)據(jù)的值為 Map 類型時,方法會遞歸處理子節(jié)點;
  • 當(dāng)值為 List 類型時,方法會遍歷列表中的每個元素并遞歸處理子節(jié)點;
  • 當(dāng)值既不是 Map 也不是 List 時,方法會根據(jù)服務(wù)號和路徑獲取脫敏規(guī)則,并對當(dāng)前節(jié)點的值進行脫敏處理,并記錄脫敏日志。

脫敏處理的具體邏輯和規(guī)則通過調(diào)用 getNestedMap 方法和 desensitizeLogic 方法來實現(xiàn),其中 getNestedMap 方法用于獲取脫敏規(guī)則,desensitizeLogic 方法用于根據(jù)脫敏規(guī)則對數(shù)據(jù)進行脫敏處理。

注:請注意本文中提供的數(shù)據(jù)樣例的層次結(jié)構(gòu)是和 YAML 中定義的結(jié)構(gòu)是一樣的,再通過上述方法遞歸后生成的鍵路徑是和從 YAML 中獲取規(guī)則所需的鍵路徑是一致的,因此可以直接調(diào)用 getNestedMapValues() 獲取脫敏規(guī)則。在實際使用中,其他數(shù)據(jù)結(jié)構(gòu)需要重寫該邏輯。

脫敏測試

編寫 Main 方法調(diào)用:

public class Demo {

    public static Map<String, Object> getData() {

        HashMap<String, Object> phone = new HashMap<>();
        phone.put("phone", "17631007015");

        HashMap<String, Object> phone2 = new HashMap<>();
        phone2.put("phone", "17631007015");

        List<HashMap<String, Object>> list = new ArrayList<>();
        list.add(phone);
        list.add(phone2);

        HashMap<String, Object> txEntity = new HashMap<>();
        txEntity.put("name", "趙士杰");
        txEntity.put("idCard", "130428197001180384");
        txEntity.put("list", list);

        HashMap<String, Object> result = new HashMap<>();

        result.put("txEntity", txEntity);

        HashMap<String, Object> txHeader = new HashMap<>();
        txHeader.put("servNo", "Y3801");
        result.put("txHeader", txHeader);

        return result;
    }

    public static void main(String[] args) {

        Map<String, Object> data = getData();
        
        // 假設(shè)data中包含接口返回的數(shù)據(jù)
        if (data.containsKey("txHeader") && data.get("txHeader") instanceof Map) {
            String servNo = ((Map<String, String>) data.get("txHeader")).get("servNo");
            DataDesensitizationUtils.parseData(data.get("txEntity"), servNo, "");
        }
        
    }

}

運行測試,控制臺輸出如下:

-----------------交易【Y3801】,字段【idCard】開始脫敏-----------------
原始值:【idCard:130428197001180384】
脫敏規(guī)則:{rule=(?<=\w{3})\w(?=\w{4}), format=+}
脫敏值:【idCard:130+++++++++++0384】
-----------------交易【Y3801】,字段【idCard】脫敏結(jié)束-----------------
-----------------交易【Y3801】,字段【list->phone】開始脫敏-----------------
原始值:【phone:17631007015】
脫敏規(guī)則:{rule=(\d{3})\d{4}(\d{4}), format=$1++++$2}
脫敏值:【phone:176++++7015】
-----------------交易【Y3801】,字段【list->phone】脫敏結(jié)束-----------------
-----------------交易【Y3801】,字段【list->phone】開始脫敏-----------------
原始值:【phone:17631007015】
脫敏規(guī)則:{rule=(\d{3})\d{4}(\d{4}), format=$1++++$2}
脫敏值:【phone:176++++7015】
-----------------交易【Y3801】,字段【list->phone】脫敏結(jié)束-----------------

數(shù)據(jù)脫敏后如下:

{
  "txEntity": {
    "idCard": "130+++++++++++0384",
    "name": "趙士杰",
    "list": [
      {
        "phone": "176++++7015"
      },
      {
        "phone": "176++++7015"
      }
    ]
  },
  "txHeader": {
    "servNo": "Y3801"
  }
}

完整工具類

封裝成完整的工具類如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-840406.html

/**
 * @ClassName DataDesensitizationUtils
 * @Description 數(shù)據(jù)脫敏工具類
 * @Author 趙士杰
 * @Date 2024/1/25 20:15
 */
@Slf4j
@SuppressWarnings("unchecked")
public class DataDesensitizationUtils {

    // YAML 文件路徑
    private static final String YAML_FILE_PATH = "/tuomin.yml";

    // 存儲解析后的 YAML 數(shù)據(jù)
    private static Map<String, Object> map;

    static {
        // 創(chuàng)建 Yaml 對象
        Yaml yaml = new Yaml();
        // 通過 getResourceAsStream 獲取 YAML 文件的輸入流
        try (InputStream in = DataDesensitizationUtils.class.getResourceAsStream(YAML_FILE_PATH)) {
            // 解析 YAML 文件為 Map 對象
            map = yaml.loadAs(in, Map.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取嵌套的 Map 數(shù)據(jù)
     *
     * @param keys 嵌套鍵路徑
     * @return 嵌套數(shù)據(jù)對應(yīng)的 Map
     */
    private static Map<String, Object> getNestedMap(String... keys) {
        return getNestedMapValues(map, keys);
    }

    /**
     * 遞歸獲取嵌套 Map 數(shù)據(jù)
     *
     * @param map  嵌套數(shù)據(jù)源的 Map
     * @param keys 嵌套鍵路徑
     * @return 嵌套數(shù)據(jù)對應(yīng)的 Map
     */
    private static Map<String, Object> getNestedMapValues(Map<String, Object> map, String... keys) {
        // 如果鍵路徑為空或者第一個鍵不在 Map 中,則返回 null
        if (keys.length == 0 || !map.containsKey(keys[0])) {
            return null;
        }

        // 獲取第一個鍵對應(yīng)的嵌套對象
        Object nestedObject = map.get(keys[0]);

        // 如果鍵路徑長度為 1,說明已經(jīng)到達最里層的嵌套 Map,直接返回該 Map 對象
        if (keys.length == 1) {
            if (nestedObject instanceof Map) {
                return (Map<String, Object>) nestedObject;
            } else {
                return null;
            }
        } else {
            // 如果嵌套對象是 Map,繼續(xù)遞歸查找下一個鍵的嵌套 Map
            if (nestedObject instanceof Map) {
                return getNestedMapValues((Map<String, Object>) nestedObject, Arrays.copyOfRange(keys, 1, keys.length));
            } else {
                // 嵌套對象既不是 Map 也不是 List,返回 null
                return null;
            }
        }
    }

    /**
     * 對指定實體數(shù)據(jù)進行脫敏處理
     *
     * @param entity 要進行脫敏處理的實體數(shù)據(jù)
     * @param servNo 當(dāng)前交易的服務(wù)號,用于記錄日志
     * @param path 當(dāng)前實體數(shù)據(jù)在整個數(shù)據(jù)結(jié)構(gòu)中的路徑,用于記錄日志
     */
    public static void parseData(Object entity, String servNo, String path) {
        if (entity instanceof Map) {
            for (Map.Entry<String, Object> entry : ((Map<String, Object>) entity).entrySet()) {
                String currentPath = path.isEmpty() ? entry.getKey() : path + "," + entry.getKey();
                if (entry.getValue() instanceof Map) {
                    parseData(entry.getValue(), servNo, currentPath);
                } else if (entry.getValue() instanceof List) {
                    for (Object item : (List) entry.getValue()) {
                        if (item instanceof Map) {
                            parseData(item, servNo, currentPath);
                        }
                    }
                } else {
                    String p = servNo + "," + currentPath;
                    String[] keyPaths = p.split(",");

                    Map<String, Object> nestedMap = getNestedMap(keyPaths);

                    if (Objects.nonNull(nestedMap)) {
                        log.info("-----------------交易【{}】,字段【{}】開始脫敏-----------------", servNo, currentPath.replace(",", "->"));
                        log.info("原始值:【{}:{}】", entry.getKey(), entry.getValue());
                        log.info("脫敏規(guī)則:{}", nestedMap);
                        String desensitized = desensitizeLogic((String) entry.getValue(), nestedMap);
                        entry.setValue(desensitized);
                        log.info("脫敏值:【{}:{}】", entry.getKey(), entry.getValue());
                        log.info("-----------------交易【{}】,字段【{}】脫敏結(jié)束-----------------", servNo, currentPath.replace(",", "->"));
                    }

                }
            }
        }
    }

    /**
     * 脫敏邏輯
     * @param data 源數(shù)據(jù)
     * @param map 脫敏規(guī)則
     * @return 脫敏后的數(shù)據(jù)
     */
    private static String desensitizeLogic(String data, Map<String, Object> map) {
        if (map.containsKey("rule")) {
            String rule = (String) map.get("rule");
            String sign = "*";
            if (map.containsKey("format")) {
                sign = (String) map.get("format");
            }
            return data.replaceAll(rule, sign);
        }
        return data;
    }

}

到了這里,關(guān)于【數(shù)據(jù)脫敏方案】不使用 AOP + 注解,使用 SpringBoot+YAML 實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 【SpringBoot應(yīng)用篇】【AOP+注解】SpringBoot+SpEL表達式基于注解實現(xiàn)權(quán)限控制

    Spring 表達式語言 SpEL 是一種非常強大的表達式語言,它支持在運行時查詢和操作對象圖。 它提供了許多高級功能,例如方法調(diào)用和基本的字符串模板功能。 表達式語言給靜態(tài)Java語言增加了動態(tài)功能。 Spring 表達式語言最初是為 Spring 社區(qū)創(chuàng)建的,它擁有一種受良好支持的表

    2024年02月20日
    瀏覽(58)
  • 【SpringBoot】AOP 自定義注解的使用詳解

    ? ? ? ? Spring 中的切面 Aspect,這是 Spring 的一大優(yōu)勢。面向切面編程往往讓我們的開發(fā)更加低耦合,也大大減少了代碼量,同時呢讓我們更專注于業(yè)務(wù)模塊的開發(fā),把那些與業(yè)務(wù)無關(guān)的東西提取出去,便于后期的維護和迭代。 ????????AOP 的全稱為 Aspect Oriented Programming,

    2024年02月05日
    瀏覽(26)
  • springboot自定義注解+aop+redis實現(xiàn)延時雙刪

    springboot自定義注解+aop+redis實現(xiàn)延時雙刪

    redis作為用的非常多的緩存數(shù)據(jù)庫,在多線程場景下,可能會出現(xiàn)數(shù)據(jù)庫與redis數(shù)據(jù)不一致的現(xiàn)象 數(shù)據(jù)不一致的現(xiàn)象:https://blog.csdn.net/m0_73700925/article/details/133447466 這里采用aop+redis來解決這個方法: 刪除緩存 更新數(shù)據(jù)庫 延時一定時間,比如500ms 刪除緩存 這里之所以要延時一

    2024年01月17日
    瀏覽(21)
  • spring boot 使用AOP+自定義注解+反射實現(xiàn)操作日志記錄修改前數(shù)據(jù)和修改后對比數(shù)據(jù),并保存至日志表

    spring boot 使用AOP+自定義注解+反射實現(xiàn)操作日志記錄修改前數(shù)據(jù)和修改后對比數(shù)據(jù),并保存至日志表

    使用FieldMeta自定義注解,看個人業(yè)務(wù)自行覺得是否需要重新定義實體 實現(xiàn)類 :通過該實現(xiàn)類獲取更新前后的數(shù)據(jù)。 該實現(xiàn)類的實現(xiàn)原理為:獲取入?yún)⒊鋈氲膇d值,獲取sqlSessionFactory,通過sqlSessionFactory獲取selectByPrimaryKey()該方法,執(zhí)行該方法可獲取id對應(yīng)數(shù)據(jù)更新操作前后的數(shù)

    2024年01月23日
    瀏覽(28)
  • Java 實現(xiàn)數(shù)據(jù)脫敏的技術(shù)方案

    數(shù)據(jù)脫敏是保護個人隱私的一種重要手段,它通過對敏感信息進行處理,將敏感信息轉(zhuǎn)換為不敏感的信息,以保護個人隱私不被泄漏。在Java中,數(shù)據(jù)脫敏也是一項非常重要的技術(shù),本文將從數(shù)據(jù)脫敏的概念、Java中的數(shù)據(jù)脫敏原理、Java中的數(shù)據(jù)脫敏方法以及如何實現(xiàn)數(shù)據(jù)脫敏

    2024年02月08日
    瀏覽(36)
  • SpringBoot實現(xiàn)返回值數(shù)據(jù)脫敏

    SpringBoot實現(xiàn)返回值數(shù)據(jù)脫敏

    介紹 SpringBoot實現(xiàn)返回數(shù)據(jù)脫敏 有時,敏感數(shù)據(jù)返回時,需要進行隱藏處理,但是如果一個字段一個字段的進行硬編碼處理的話,不僅增加了工作量,而且后期需求變動的時候,更加是地獄般的工作量變更。 下面,通過身份證,姓名,密碼,手機號等等示例去演示脫敏的流程

    2024年02月15日
    瀏覽(17)
  • SpringBoot自定義注解+AOP+redis實現(xiàn)防接口冪等性重復(fù)提交,從概念到實戰(zhàn)

    本文為千鋒教育技術(shù)團獨家創(chuàng)作,更多技術(shù)類知識干貨,點個關(guān)注持續(xù)追更~ 接口冪等性是Web開發(fā)中非常重要的一個概念,它可以保證多次調(diào)用同一個接口不會對結(jié)果產(chǎn)生影響。如果你想了解更多關(guān)于接口冪等性的知識,那么本文就是一個不錯的起點。 在Web開發(fā)中,我們經(jīng)常

    2024年02月03日
    瀏覽(26)
  • 【Spring】使用自定義注解方式實現(xiàn)AOP鑒權(quán)

    AOP,是一種面向切面編程,可以通過預(yù)編譯方式和運行期間動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術(shù)。 在軟件開發(fā)中,鑒權(quán)(Authentication)是一項非常重要的安全措施,用于驗證用戶身份和權(quán)限。在應(yīng)用程序中,我們通常會使用AOP(Aspect-Oriented Programming)來實現(xiàn)鑒權(quán)功能

    2024年02月11日
    瀏覽(20)
  • SpringBoot中優(yōu)雅的實現(xiàn)隱私數(shù)據(jù)脫敏(提供Gitee源碼)

    前言:在實際項目開發(fā)中,可能會對一些用戶的隱私信息進行脫敏操作,傳統(tǒng)的方式很多都是用replace方法進行手動替換,這樣會由很多冗余的代碼并且后續(xù)也不好維護,本期就講解一下如何在SpringBoot中優(yōu)雅的通過序列化的方式去實現(xiàn)數(shù)據(jù)的脫敏操作! 目錄 一、導(dǎo)入pom依賴

    2024年02月12日
    瀏覽(16)
  • SpringBoot利用自定義json序列化器實現(xiàn)敏感字段數(shù)據(jù)脫敏

    物料準(zhǔn)備: 1.hutool依賴 2.自定義的jackson序列化器 3.測試@JsonSerialize效果 因為案例代碼用到了hutool提供的DesensitizedUtil數(shù)據(jù)脫敏工具類,這里要引入hutool的依賴。 如果你需要自定義 數(shù)據(jù)脫敏的邏輯,可以不引入這個依賴 自定義一個手機號脫敏序列化器 自定義一個郵箱脫敏序列化

    2024年02月12日
    瀏覽(21)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包