政府網(wǎng)站建設(shè)的存在問題網(wǎng)址查詢工具
文章目錄
- 引入
- 認(rèn)識(shí) YAML 格式規(guī)范
- 定義脫敏規(guī)則格式
- 脫敏邏輯實(shí)現(xiàn)
- 讀取 YAML 配置文件獲取脫敏規(guī)則
- 通過鍵路徑獲取對(duì)應(yīng)字段規(guī)則
- 原始
- 優(yōu)化后
- 對(duì)數(shù)據(jù)進(jìn)行脫敏處理
- 遞歸生成字段對(duì)應(yīng)的鍵路徑
- 脫敏測(cè)試
- 完整工具類
引入
在項(xiàng)目中遇到一個(gè)需求,需要對(duì)交易接口返回結(jié)果中的指定字段進(jìn)行脫敏操作,但又不能使用AOP+注解的形式,于是決定使用一種比較笨的方法:
- 首先將所有需要脫敏字段及其對(duì)應(yīng)脫敏規(guī)則存儲(chǔ)到 Map 中。
- 在接口返回時(shí),遍歷結(jié)果中的所有字段,判斷字段名在 Map 中是否存在:
- 如果不存在:說明該字段不需要脫敏,不做處理即可。
- 如果存在:說明該字段需要脫敏,從 Map 中獲取對(duì)應(yīng)的脫敏規(guī)則進(jìn)行脫敏。
- 最后返回脫敏之后的結(jié)果。
認(rèn)識(shí) YAML 格式規(guī)范
由于返回的結(jié)果涉及到嵌套 Map,所以決定采用 YAML 格式的文件存儲(chǔ)脫敏規(guī)則,那么為了大家統(tǒng)一維護(hù)和開發(fā),就需要大家對(duì) YAML 格式進(jìn)行了解,遵守規(guī)范,不易出錯(cuò),少走彎路。
YAML(YAML Ain’t Markup Language)與傳統(tǒng)的 JSON、XML 和 Properties 文件一樣,都是用于數(shù)據(jù)序列化的格式,常用于配置文件和數(shù)據(jù)傳輸。
相比于其他格式,YAML 是一種輕量級(jí)的數(shù)據(jù)序列化格式,它的設(shè)計(jì)初衷是為了簡(jiǎn)化復(fù)雜性,提高人類可讀性,并且易于實(shí)現(xiàn)和解析。
-
與 JSON 相比:YAML 在語法上更為靈活,允許使用更簡(jiǎn)潔的方式來表示數(shù)據(jù)結(jié)構(gòu)。
-
與 XML 相比:YAML 的語法更為簡(jiǎn)潔,沒有繁瑣的標(biāo)簽和尖括號(hào)。
-
與 Properties 相比:YAML 支持更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),包括嵌套的鍵值對(duì)和列表。
除此之外,YAML 還支持跨平臺(tái)、跨語言,可以被多種編程語言解析,這使得YAML非常適合用于不同語言之間的數(shù)據(jù)傳輸和交換。
YAML 文件的語法非常簡(jiǎn)潔明了,以下是它的語法規(guī)范:
-
基本語法:
- 使用 縮進(jìn)表示層級(jí)關(guān)系,可以使用空格或制表符進(jìn)行縮進(jìn),但不能混用。
- 使用冒號(hào)(
:
)表示鍵值對(duì),鍵值對(duì)之間使用換行分隔。 - 使用破折號(hào)(
-
)表示列表項(xiàng),列表項(xiàng)之間也使用換行分隔。
# 使用縮進(jìn)表示層級(jí)關(guān)系 server:port: 8080# 使用冒號(hào)表示鍵值對(duì) name: John Smith age: 30# 使用破折號(hào)表示列表項(xiàng) hobbies:- reading- hiking- swimming
-
注釋:
- 使用井號(hào)(
#
)表示注釋,在#
后面的內(nèi)容被視為注釋,可以出現(xiàn)在行首或行尾。
# 這是一個(gè)注釋 name: John Smith age: 30 # 這也是一個(gè)注釋
- 使用井號(hào)(
-
字符串:
- 字符串可以使用單引號(hào)或雙引號(hào)括起來,也可以不使用引號(hào)。
- 使用雙引號(hào)時(shí),可以使用轉(zhuǎn)義字符(如
\n
表示換行)和轉(zhuǎn)義序列(如\u
表示Unicode
字符)。
# 使用雙引號(hào)表示字符串 name: "John Smith"# 使用單引號(hào)表示字符串 nickname: 'Johnny'
-
鍵值對(duì):
- 鍵值對(duì)使用冒號(hào)(
:
)表示,鍵和值之間使用一個(gè) 空格 分隔。 - 鍵可以是字符串或純量(如整數(shù)、布爾值等)。
- 值可以是字符串、純量、列表或嵌套的鍵值對(duì)。
# 鍵和值之間使用一個(gè)空格分隔 name: John Smith# 鍵可以是字符串或純量 age: 30# 值可以是字符串、純量、列表或嵌套的鍵值對(duì) address:city: San Franciscostate: Californiazip: 94107
- 鍵值對(duì)使用冒號(hào)(
-
列表:
- 使用破折號(hào)(
-
)表示列表項(xiàng)。 - 列表項(xiàng)可以是字符串、純量或嵌套的列表或鍵值對(duì)。
# 使用破折號(hào)表示列表項(xiàng) hobbies:- reading- hiking- swimming# 列表項(xiàng)可以是字符串、純量或嵌套的列表或鍵值對(duì) people:- name: John Smithage: 30- name: Jane Doeage: 25
- 使用破折號(hào)(
-
引用:
- 使用
&
表示引用,使用*
表示引用的內(nèi)容。
# 使用&表示引用 address: &myaddresscity: San Franciscostate: Californiazip: 94107# 使用*表示引用的內(nèi)容 shippingAddress: *myaddress
- 使用
-
多行文本塊:
- 使用
|
保留換行符,保留文本塊的精確格式。 - 使用
>
折疊換行符,將文本塊折疊成一行,并根據(jù)內(nèi)容自動(dòng)換行。
# 使用|保留換行符 description: |This is amulti-linestring.# 使用>折疊換行符 summary: >This is a summarythat may containline breaks.
- 使用
-
數(shù)據(jù)類型:
- YAML支持多種數(shù)據(jù)類型,包括字符串、整數(shù)、浮點(diǎn)數(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'
-
多文件:
- 可以使用—表示多個(gè) YAML 文件之間的分隔符。每個(gè)文件可以使用任何 YAML 語法。
# 第一個(gè)YAML文件 name: John Smith age: 30---# 第二個(gè)YAML文件 hobbies:- reading- hiking- swimming
定義脫敏規(guī)則格式
對(duì)于數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單的接口返回結(jié)果,脫敏規(guī)則格式定義為【交易號(hào)->字段->規(guī)則】:
交易號(hào):字段名:規(guī)則: '/^(1[3-9][0-9])\d{4}(\d{4}$)/'
同時(shí)接口返回的結(jié)果中可能用有嵌套列表,那么針對(duì)這種復(fù)雜的結(jié)構(gòu)就定義格式為【交易號(hào)->字段(列表)->字段->規(guī)則】,即:
交易號(hào):字段名(列表):字段名:規(guī)則: '/^(1[3-9][0-9])\d{4}(\d{4}$)/'
使用這種層級(jí)結(jié)構(gòu),我們完全可以通過 Map.get("Key")
的形式獲取到指定交易,指定字段的脫敏規(guī)則。
脫敏邏輯實(shí)現(xiàn)
讀取 YAML 配置文件獲取脫敏規(guī)則
-
首先創(chuàng)建 YAML 文件
desensitize.yml
添加對(duì)應(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
工具類中,我們需要實(shí)現(xiàn)在項(xiàng)目啟動(dòng)時(shí),讀取desensitize.yml
文件中的內(nèi)容,并轉(zhuǎn)為我們想要的 Map 鍵值對(duì)數(shù)據(jù)類型:/*** 讀取yaml文件內(nèi)容并轉(zhuǎn)為Map* @param yamlFile yaml文件路徑* @return Map對(duì)象*/ 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 格式進(jìn)行解析,并將解析結(jié)果轉(zhuǎn)換為指定的Map.class
類型。最后使用 try-with-resources 語句來自動(dòng)關(guān)閉輸入流。
通過鍵路徑獲取對(duì)應(yīng)字段規(guī)則
原始
-
在上文中我們已經(jīng)將
desensitize.yml
文件中所有的脫敏規(guī)則都以 key-Value 的形式存儲(chǔ)到了 Map 中,因此我們只需要通過 Key 從 Map 中獲取即可。接下來編寫方法通過 Key 獲取指定字段對(duì)應(yīng)脫敏規(guī)則:public static void main(String[] args) {// 加載 YAML 文件并獲取頂層的 Map 對(duì)象,路徑基于 resources 目錄Map<String, Object> yamlMap = loadYaml("/desensitize.yml");System.out.println(yamlMap);// 從頂層的 Map 中獲取名為 "Y3800" 的嵌套 MapMap<String, Object> Y3800= (Map<String, Object>) yamlMap.get("Y3800");System.out.println(Y3800);// 從 "Y3800" 的嵌套 Map 中獲取名為 "phone" 的嵌套 MapMap<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
層級(jí)下的數(shù)據(jù):{"phone": {"rule": "(\\d{3})\\d{4}(\\d{4})","format": "$1****$2"},"idCard": {"rule": "(?<=\\w{3})\\w(?=\\w{4})","format": "*"} }
-
輸出
phone
層級(jí)下的數(shù)據(jù):{"rule": "(\\d{3})\\d{4}(\\d{4})","format": "$1****$2" }
-
在這里,我們需要仔細(xì)思考一下,在我們通過 Key 獲取指定層級(jí)下的數(shù)據(jù)時(shí),我們需要不斷的調(diào)用 Map.get("Key")
方法,即結(jié)構(gòu)每嵌套一次,就需要一次 getKey,那么這里是否有優(yōu)化的方法呢?
答案是:有的,因?yàn)橛袉栴}就會(huì)有答案。
優(yōu)化后
首先我們需要先了解一個(gè)概念:
Y3800:phone:rule: "(\\d{3})\\d{4}(\\d{4})"format: "$1****$2"
當(dāng)我們要從上述數(shù)據(jù)中獲取 phone
的脫敏規(guī)則時(shí),我們需要先從 Map 中 get("Y3800")
獲取 Y3800
下的數(shù)據(jù),再通過 get("phone")
獲取 phone
下的規(guī)則,那么 Y3800->phone
就是 phone
的鍵路徑。
基于此,我們可以實(shí)現(xiàn)這樣一個(gè)方法,我們直接給出指定字段的鍵路徑,在方法中通過遞歸的方式從 Map 中獲取到該鍵路徑下的所有數(shù)據(jù),然后返回即可。
即優(yōu)化思路為:通過遞歸和判斷來遍歷嵌套的 Map,直到找到鍵路徑所對(duì)應(yīng)的最里層的嵌套 Map,并返回該 Map 對(duì)象。
優(yōu)化后方法如下:
/*** 遞歸獲取嵌套 Map 數(shù)據(jù)** @param map 嵌套數(shù)據(jù)源的 Map* @param keys 嵌套鍵路徑* @return 嵌套數(shù)據(jù)對(duì)應(yīng)的 Map*/
@SuppressWarnings("unchecked")
public static Map<String, Object> getNestedMapValues(Map<String, Object> map, String... keys) {// 如果鍵路徑為空或者第一個(gè)鍵不在 Map 中,則返回 nullif (keys.length == 0 || !map.containsKey(keys[0])) {return null;}// 獲取第一個(gè)鍵對(duì)應(yīng)的嵌套對(duì)象Object nestedObject = map.get(keys[0]);// 如果鍵路徑長(zhǎng)度為 1,說明已經(jīng)到達(dá)最里層的嵌套 Map,直接返回該 Map 對(duì)象if (keys.length == 1) {if (nestedObject instanceof Map) {return (Map<String, Object>) nestedObject;} else {return null;}} else {// 如果嵌套對(duì)象是 Map,繼續(xù)遞歸查找下一個(gè)鍵的嵌套 Mapif (nestedObject instanceof Map) {return getNestedMapValues((Map<String, Object>) nestedObject, Arrays.copyOfRange(keys, 1, keys.length));} else {// 嵌套對(duì)象既不是 Map 也不是 List,返回 nullreturn null;}}
}
調(diào)用方法時(shí)傳入 Key 的嵌套路徑即可:
public static void main(String[] args) {// 加載 YAML 文件并獲取頂層的 Map 對(duì)象Map<String, Object> yamlMap = loadYaml("/desensitize.yml");System.out.println(yamlMap);// 獲取 Y3800 -> phone 下的數(shù)據(jù)轉(zhuǎn)為 MapMap<String, Object> y3800PhoneMap = YamlUtils.getNestedMap(yamlMap, "Y3800", "phone");System.out.println("Y3800 -> phone : " + y3800NameMap);
}
具體來說,主要分為以下幾步:
- 首先判斷鍵路徑是否為空或者第一個(gè)鍵是否在 Map 中。如果鍵路徑為空或者第一個(gè)鍵不在 Map 中,則返回 null。
- 獲取第一個(gè)鍵對(duì)應(yīng)的嵌套對(duì)象。通過 get 方法獲取第一個(gè)鍵對(duì)應(yīng)的嵌套對(duì)象。
- 判斷是否到達(dá)最里層的嵌套 Map。如果鍵路徑長(zhǎng)度為 1,說明已經(jīng)到達(dá)最里層的嵌套 Map,直接返回該 Map 對(duì)象。
- 繼續(xù)遞歸查找下一個(gè)鍵的嵌套 Map。如果嵌套對(duì)象是 Map,則繼續(xù)遞歸查找下一個(gè)鍵的嵌套 Map。
- 返回結(jié)果。返回遞歸查找的結(jié)果。
對(duì)數(shù)據(jù)進(jìn)行脫敏處理
獲取到字段的脫敏規(guī)則后,我們就可以編寫方法實(shí)現(xiàn)對(duì)源數(shù)據(jù)做脫敏處理,脫敏方法如下:
/*** 使用指定規(guī)則對(duì)數(shù)據(jù)進(jìn)行脫敏處理** @param data 要進(jìn)行脫敏處理的數(shù)據(jù)* @param map 包含脫敏規(guī)則和格式的參數(shù)映射* - "rule" 表示脫敏規(guī)則的正則表達(dá)式* - "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;
}
遞歸生成字段對(duì)應(yīng)的鍵路徑
目前我們已經(jīng)實(shí)現(xiàn)了通過字段的鍵路徑獲取到該字段對(duì)應(yīng)規(guī)則的方法 getNestedMapValues()
,那么接下來我們只需要生成字段對(duì)應(yīng)的鍵路徑,然后調(diào)用方法 getNestedMapValues()
獲取到脫敏規(guī)則后調(diào)用 desensitizeLogic()
對(duì)源數(shù)據(jù)進(jìn)行脫敏即可。
提供源數(shù)據(jù)格式如下:
{"txEntity": {"idCard": "130428197001180384","name": "趙士杰","list": [{"phone": "17631007015"},{"phone": "17631007015"}]},"txHeader": {"servNo": "Y3801"}
}
根據(jù)上述數(shù)據(jù)結(jié)構(gòu),首先我們需要從 txHeader
中獲取 servNo
,之后遞歸遍歷 txEntity
中的元素即可。
具體方法如下:
/*** 對(duì)指定實(shí)體數(shù)據(jù)進(jìn)行脫敏處理** @param entity 要進(jìn)行脫敏處理的實(shí)體數(shù)據(jù)* @param servNo 當(dāng)前交易的服務(wù)號(hào),用于記錄日志* @param path 當(dāng)前實(shí)體數(shù)據(jù)在整個(gè)數(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()) {// 計(jì)算當(dāng)前鍵值對(duì)在整個(gè)數(shù)據(jù)結(jié)構(gòu)中的路徑String currentPath = path.isEmpty() ? entry.getKey() : path + "," + entry.getKey();if (entry.getValue() instanceof Map) {// 如果當(dāng)前值是 Map 類型,則遞歸處理子節(jié)點(diǎn)parseData(entry.getValue(), servNo, currentPath);} else if (entry.getValue() instanceof List) {// 如果當(dāng)前值是 List 類型,則遍歷列表中的每個(gè)元素并遞歸處理子節(jié)點(diǎn)for (Object item : (List) entry.getValue()) {if (item instanceof Map) {parseData(item, servNo, currentPath);}}} else {// 如果當(dāng)前值不是 Map 或 List,則進(jìn)行脫敏處理String p = servNo + "," +currentPath;String[] keyPaths = p.split(",");// 獲取當(dāng)前節(jié)點(diǎn)的脫敏規(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);// 對(duì)當(dāng)前節(jié)點(diǎn)的值進(jìn)行脫敏處理String desensitized = desensitizeLogic((String) entry.getValue(), nestedMap);entry.setValue(desensitized);// 記錄日志log.info("脫敏值:【{}:{}】",entry.getKey(),entry.getValue());log.info("-----------------交易【{}】,字段【{}】脫敏結(jié)束-----------------",servNo,currentPath.replace(",","->"));}}}}
}
該方法接收一個(gè)實(shí)體數(shù)據(jù) entity
,一個(gè)服務(wù)號(hào) servNo
和一個(gè)路徑 path
作為參數(shù)。在方法體內(nèi),會(huì)遍歷實(shí)體數(shù)據(jù)的鍵值對(duì),并根據(jù)具體情況遞歸處理子節(jié)點(diǎn)或進(jìn)行脫敏處理。
- 當(dāng)實(shí)體數(shù)據(jù)的值為 Map 類型時(shí),方法會(huì)遞歸處理子節(jié)點(diǎn);
- 當(dāng)值為 List 類型時(shí),方法會(huì)遍歷列表中的每個(gè)元素并遞歸處理子節(jié)點(diǎn);
- 當(dāng)值既不是 Map 也不是 List 時(shí),方法會(huì)根據(jù)服務(wù)號(hào)和路徑獲取脫敏規(guī)則,并對(duì)當(dāng)前節(jié)點(diǎn)的值進(jìn)行脫敏處理,并記錄脫敏日志。
脫敏處理的具體邏輯和規(guī)則通過調(diào)用 getNestedMap
方法和 desensitizeLogic
方法來實(shí)現(xiàn),其中 getNestedMap
方法用于獲取脫敏規(guī)則,desensitizeLogic
方法用于根據(jù)脫敏規(guī)則對(duì)數(shù)據(jù)進(jìn)行脫敏處理。
注:請(qǐng)注意本文中提供的數(shù)據(jù)樣例的層次結(jié)構(gòu)是和 YAML 中定義的結(jié)構(gòu)是一樣的,再通過上述方法遞歸后生成的鍵路徑是和從 YAML 中獲取規(guī)則所需的鍵路徑是一致的,因此可以直接調(diào)用 getNestedMapValues()
獲取脫敏規(guī)則。在實(shí)際使用中,其他數(shù)據(jù)結(jié)構(gòu)需要重寫該邏輯。
脫敏測(cè)試
編寫 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, "");}}}
運(yùn)行測(cè)試,控制臺(tái)輸出如下:
-----------------交易【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"}
}
完整工具類
封裝成完整的工具類如下:
/*** @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";// 存儲(chǔ)解析后的 YAML 數(shù)據(jù)private static Map<String, Object> map;static {// 創(chuàng)建 Yaml 對(duì)象Yaml yaml = new Yaml();// 通過 getResourceAsStream 獲取 YAML 文件的輸入流try (InputStream in = DataDesensitizationUtils.class.getResourceAsStream(YAML_FILE_PATH)) {// 解析 YAML 文件為 Map 對(duì)象map = yaml.loadAs(in, Map.class);} catch (Exception e) {e.printStackTrace();}}/*** 獲取嵌套的 Map 數(shù)據(jù)** @param keys 嵌套鍵路徑* @return 嵌套數(shù)據(jù)對(duì)應(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ù)對(duì)應(yīng)的 Map*/private static Map<String, Object> getNestedMapValues(Map<String, Object> map, String... keys) {// 如果鍵路徑為空或者第一個(gè)鍵不在 Map 中,則返回 nullif (keys.length == 0 || !map.containsKey(keys[0])) {return null;}// 獲取第一個(gè)鍵對(duì)應(yīng)的嵌套對(duì)象Object nestedObject = map.get(keys[0]);// 如果鍵路徑長(zhǎng)度為 1,說明已經(jīng)到達(dá)最里層的嵌套 Map,直接返回該 Map 對(duì)象if (keys.length == 1) {if (nestedObject instanceof Map) {return (Map<String, Object>) nestedObject;} else {return null;}} else {// 如果嵌套對(duì)象是 Map,繼續(xù)遞歸查找下一個(gè)鍵的嵌套 Mapif (nestedObject instanceof Map) {return getNestedMapValues((Map<String, Object>) nestedObject, Arrays.copyOfRange(keys, 1, keys.length));} else {// 嵌套對(duì)象既不是 Map 也不是 List,返回 nullreturn null;}}}/*** 對(duì)指定實(shí)體數(shù)據(jù)進(jìn)行脫敏處理** @param entity 要進(jìn)行脫敏處理的實(shí)體數(shù)據(jù)* @param servNo 當(dāng)前交易的服務(wù)號(hào),用于記錄日志* @param path 當(dāng)前實(shí)體數(shù)據(jù)在整個(gè)數(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;}}