最无用的工具类封装——DesensitizedUtils脱敏Json字段值

时间:2025-03-11 20:17:52
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(); } }