引入
在項目中遇到一個需求,需要對交易接口返回結(jié)果中的指定字段進行脫敏操作,但又不能使用AOP+注解的形式,于是決定使用一種比較笨的方法:
- 首先將所有需要脫敏字段及其對應(yīng)脫敏規(guī)則存儲到 Map 中。
- 在接口返回時,遍歷結(jié)果中的所有字段,判斷字段名在 Map 中是否存在:
- 如果不存在:說明該字段不需要脫敏,不做處理即可。
- 如果存在:說明該字段需要脫敏,從 Map 中獲取對應(yīng)的脫敏規(guī)則進行脫敏。
- 最后返回脫敏之后的結(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ī)范:
-
基本語法:
- 使用 縮進表示層級關(guān)系,可以使用空格或制表符進行縮進,但不能混用。
- 使用冒號(
:
)表示鍵值對,鍵值對之間使用換行分隔。 - 使用破折號(
-
)表示列表項,列表項之間也使用換行分隔。
# 使用縮進表示層級關(guān)系 server: port: 8080 # 使用冒號表示鍵值對 name: John Smith age: 30 # 使用破折號表示列表項 hobbies: - reading - hiking - swimming
-
注釋:
- 使用井號(
#
)表示注釋,在#
后面的內(nèi)容被視為注釋,可以出現(xiàn)在行首或行尾。
# 這是一個注釋 name: John Smith age: 30 # 這也是一個注釋
- 使用井號(
-
字符串:
- 字符串可以使用單引號或雙引號括起來,也可以不使用引號。
- 使用雙引號時,可以使用轉(zhuǎn)義字符(如
\n
表示換行)和轉(zhuǎn)義序列(如\u
表示Unicode
字符)。
# 使用雙引號表示字符串 name: "John Smith" # 使用單引號表示字符串 nickname: 'Johnny'
-
鍵值對:
- 鍵值對使用冒號(
:
)表示,鍵和值之間使用一個 空格 分隔。 - 鍵可以是字符串或純量(如整數(shù)、布爾值等)。
- 值可以是字符串、純量、列表或嵌套的鍵值對。
# 鍵和值之間使用一個空格分隔 name: John Smith # 鍵可以是字符串或純量 age: 30 # 值可以是字符串、純量、列表或嵌套的鍵值對 address: city: San Francisco state: California zip: 94107
- 鍵值對使用冒號(
-
列表:
- 使用破折號(
-
)表示列表項。 - 列表項可以是字符串、純量或嵌套的列表或鍵值對。
# 使用破折號表示列表項 hobbies: - reading - hiking - swimming # 列表項可以是字符串、純量或嵌套的列表或鍵值對 people: - name: John Smith age: 30 - name: Jane Doe age: 25
- 使用破折號(
-
引用:
- 使用
&
表示引用,使用*
表示引用的內(nèi)容。
# 使用&表示引用 address: &myaddress city: San Francisco state: California zip: 94107 # 使用*表示引用的內(nèi)容 shippingAddress: *myaddress
- 使用
-
多行文本塊:
- 使用
|
保留換行符,保留文本塊的精確格式。 - 使用
>
折疊換行符,將文本塊折疊成一行,并根據(jù)內(nèi)容自動換行。
# 使用|保留換行符 description: | This is a multi-line string. # 使用>折疊換行符 summary: > This is a summary that may contain line breaks.
- 使用
-
數(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'
-
多文件:
- 可以使用—表示多個 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ī)則
-
首先創(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"
-
定義脫敏工具類
DataDesensitizationUtils
編寫我們的脫敏邏輯:public class DataDesensitizationUtils { }
-
在
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ī)則
原始
-
在上文中我們已經(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);
}
具體來說,主要分為以下幾步:
- 首先判斷鍵路徑是否為空或者第一個鍵是否在 Map 中。如果鍵路徑為空或者第一個鍵不在 Map 中,則返回 null。
- 獲取第一個鍵對應(yīng)的嵌套對象。通過 get 方法獲取第一個鍵對應(yīng)的嵌套對象。
- 判斷是否到達最里層的嵌套 Map。如果鍵路徑長度為 1,說明已經(jīng)到達最里層的嵌套 Map,直接返回該 Map 對象。
- 繼續(xù)遞歸查找下一個鍵的嵌套 Map。如果嵌套對象是 Map,則繼續(xù)遞歸查找下一個鍵的嵌套 Map。
- 返回結(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ù)脫敏后如下:文章來源:http://www.zghlxwxcb.cn/news/detail-840406.html
{
"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)!