最无用的工具类封装——DesensitizedUtils脱敏Json字段值
package cn.texous.demo.utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import cn.texous.demo.utils.StringUtils;
import cn.texous.demo.utils.ValidateUtils;
import lombok.extern.slf4j.Slf4j;
import javax.validation.constraints.NotNull;
import java.util.*;
/**
* 数据脱敏工具
*
* @author texousliu
* @since 2022-10-14
*/
@Slf4j
public class DesensitizedUtils {
private static final ObjectMapper OM = new ObjectMapper();
/**
* read jsonString to JsonNode
*
* @param jsonString jsonString
* @return JsonNode
*/
public static JsonNode readTree(String jsonString) {
JsonNode jsonNode = null;
try {
jsonNode = OM.readTree(jsonString);
} catch (JsonProcessingException e) {
log.error("Read jsonString to JsonNode error", e);
}
return jsonNode;
}
/**
* 对 jsonString 的 key 对应的 value 进行脱敏
* <p>
* keys 表示不区分层级,key 一致则替换。<br>
* detailKeys 表示详细路径 key,例如 data 或者
*
* @param jsonString jsonString
* @param keys 全局 key,匹配则替换
* @param detailKeys 详细路径key,层级关系及key匹配才替换
* @return result
*/
public static String desensitizedJsonString(String jsonString,
Collection<String> keys,
Collection<String> detailKeys) {
if (StringUtils.isBlank(jsonString)) {
return jsonString;
}
JsonNode jsonNode = readTree(jsonString);
return desensitizedJsonNode(jsonNode, keys, detailKeys);
}
/**
* 对 jsonNode 的 key 对应的 value 进行脱敏
* <p>
* keys 表示不区分层级,key 一致则替换。<br>
* detailKeys 表示详细路径 key,例如 data 或者
*
* @param jsonNode jsonNode
* @param keys 全局 key,匹配则替换
* @param detailKeys 详细路径key,层级关系及key匹配才替换
* @return result
*/
public static String desensitizedJsonNode(@NotNull JsonNode jsonNode,
Collection<String> keys,
Collection<String> detailKeys) {
if (!StringUtils.isEmpty(detailKeys)) {
desensitizedDetailKeyValue(jsonNode, detailKeys);
}
if (!StringUtils.isEmpty(keys)) {
desensitizedKeysValue(jsonNode, keys);
}
return jsonNode.toString();
}
/**
* desensitized jsonNode keys value
*
* @param jsonNode jsonNode
* @param keys keys
*/
private static void desensitizedKeysValue(@NotNull JsonNode jsonNode, @NotNull Collection<String> keys) {
if (jsonNode == null) return;
if (jsonNode.isObject()) {
ObjectNode on = (ObjectNode) jsonNode;
Iterator<Map.Entry<String, JsonNode>> iterator = on.fields();
while (iterator.hasNext()) {
Map.Entry<String, JsonNode> next = iterator.next();
if (next.getValue().isValueNode()) {
if (keys.contains(next.getKey())) {
desensitizedValueNode(on, next.getKey(), next.getValue());
}
} else {
desensitizedKeysValue(next.getValue(), keys);
}
}
} else if (jsonNode.isArray()) {
jsonNode.forEach(j -> desensitizedKeysValue(j, keys));
}
}
private static void desensitizedDetailKeyValue(@NotNull JsonNode jsonNode, @NotNull Collection<String> detailKey) {
for (String field : detailKey) {
String[] split = field.split("\\.");
desensitizedDetailKeyValue(jsonNode, split, 0, split.length - 1);
}
}
private static void desensitizedDetailKeyValue(JsonNode jsonNode, String[] fields, int fieldIndex, int lastFieldIndex) {
int tempFieldIndex = fieldIndex;
if (jsonNode.isObject()) {
if (fieldIndex < lastFieldIndex) {
desensitizedDetailKeyValue(jsonNode.get(fields[fieldIndex]), fields, ++fieldIndex, lastFieldIndex);
} else {
JsonNode jn = jsonNode.get(fields[fieldIndex]);
desensitizedValueNode((ObjectNode) jsonNode, fields[fieldIndex], jn);
}
} else if (jsonNode.isArray()) {
jsonNode.forEach(j -> desensitizedDetailKeyValue(j, fields, tempFieldIndex, lastFieldIndex));
}
}
/**
* Desensitized value
*
* @param on value parent node
* @param key value key
* @param value value
*/
private static void desensitizedValueNode(ObjectNode on, String key, JsonNode value) {
if (value == null) {
return;
}
if (value.isTextual()) {
on.replace(key, new TextNode(desensitizedString(value.asText())));
} else {
on.replace(key, NullNode.getInstance());
}
}
/**
* 字符串脱敏
*
* @param text 账号
* @return 结果
*/
private static String desensitizedString(String text) {
return ValidateUtils.isMobile(text)
? desensitizedMobile(text)
: ValidateUtils.isEmail(text)
? desensitizedMail(text)
: desensitizedOther(text);
}
/**
* 手机号脱敏
*
* @param mobile 手机号
* @return 结果
*/
private static String desensitizedMobile(String mobile) {
return replace(mobile, 3, mobile.length() - 4, '*');
}
/**
* 邮箱脱敏
*
* @param mail 手机号
* @return 结果
*/
private static String desensitizedMail(String mail) {
if (mail != null && mail.contains("@")) {
String[] split = mail.split("@");
StringBuilder sb = new StringBuilder();
for (String s : split) {
sb.append(s.length() > 2 ? replace(s, 2, s.length(), '*') : s);
sb.append("@");
}
return sb.substring(0, sb.length() - 1);
}
return mail;
}
/**
* 字符串脱敏
*
* @param other 字符串
* @return 脱敏结果
*/
private static String desensitizedOther(String other) {
if (other != null && other.length() > 3) {
return replace(other, 3, other.length(), '*');
}
return other;
}
/**
* 拷贝自 Hutool:
* 替换指定字符串的指定区间内字符为固定字符<br>
* 此方法使用{@link String#codePoints()}完成拆分替换
*
* @param str 字符串
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @param replacedChar 被替换的字符
* @return 替换后的字符串
* @since 3.2.1
*/
private static String replace(CharSequence str, int startInclude, int endExclude, char replacedChar) {
if (StringUtils.isEmpty(str)) {
return str(str);
}
final String originalStr = str(str);
int[] strCodePoints = originalStr.codePoints().toArray();
final int strLength = strCodePoints.length;
if (startInclude > strLength) {
return originalStr;
}
if (endExclude > strLength) {
endExclude = strLength;
}
if (startInclude > endExclude) {
// 如果起始位置大于结束位置,不替换
return originalStr;
}
final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < strLength; i++) {
if (i >= startInclude && i < endExclude) {
stringBuilder.append(replacedChar);
} else {
stringBuilder.append(new String(strCodePoints, i, 1));
}
}
return stringBuilder.toString();
}
/**
* 拷贝自 Hutool:
*
* @param cs CharSequence
* @return String
*/
private static String str(CharSequence cs) {
return null == cs ? null : cs.toString();
}
}