diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/RichTextMaskingUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/RichTextMaskingUtil.java new file mode 100644 index 000000000..d776e6f32 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/RichTextMaskingUtil.java @@ -0,0 +1,162 @@ +package org.dromara.hutool.core.data; + +import org.dromara.hutool.core.data.masking.RichTextMaskingProcessor; +import org.dromara.hutool.core.data.masking.RichTextMaskingRule; + +/** + * 富文本脱敏工具类,提供对富文本内容的脱敏处理功能 + * + * @author xjf + */ +public class RichTextMaskingUtil { + + /** + * 默认的富文本脱敏处理器 + */ + private static final RichTextMaskingProcessor DEFAULT_PROCESSOR = createDefaultProcessor(); + + /** + * 创建默认的富文本脱敏处理器 + * + * @return 默认的富文本脱敏处理器 + */ + private static RichTextMaskingProcessor createDefaultProcessor() { + RichTextMaskingProcessor processor = new RichTextMaskingProcessor(true); + + // 添加一些常用的脱敏规则 + + // 邮箱脱敏规则 + processor.addRule(new RichTextMaskingRule( + "邮箱", + "[\\w.-]+@[\\w.-]+\\.\\w+", + RichTextMaskingRule.MaskType.PARTIAL, + "[邮箱已隐藏]") + .setPreserveLeft(1) + .setPreserveRight(0) + .setMaskChar('*')); + + // 网址脱敏规则 + processor.addRule(new RichTextMaskingRule( + "网址", + "https?://[\\w.-]+(?:/[\\w.-]*)*", + RichTextMaskingRule.MaskType.REPLACE, + "[网址已隐藏]")); + + // 敏感词脱敏规则(示例) + processor.addRule(new RichTextMaskingRule( + "敏感词", + "(机密|绝密|内部资料|秘密|保密)", + RichTextMaskingRule.MaskType.FULL, + "***") + .setMaskChar('*')); + + return processor; + } + + /** + * 对富文本内容进行脱敏处理 + * + * @param text 富文本内容 + * @return 脱敏后的文本 + */ + public static String mask(String text) { + return DEFAULT_PROCESSOR.mask(text); + } + + /** + * 使用自定义处理器对富文本内容进行脱敏处理 + * + * @param text 富文本内容 + * @param processor 自定义处理器 + * @return 脱敏后的文本 + */ + public static String mask(String text, RichTextMaskingProcessor processor) { + return processor.mask(text); + } + + /** + * 创建一个新的富文本脱敏处理器 + * + * @param preserveHtmlTags 是否保留HTML标签 + * @return 富文本脱敏处理器 + */ + public static RichTextMaskingProcessor createProcessor(boolean preserveHtmlTags) { + return new RichTextMaskingProcessor(preserveHtmlTags); + } + + /** + * 创建一个邮箱脱敏规则 + * + * @return 邮箱脱敏规则 + */ + public static RichTextMaskingRule createEmailRule() { + return new RichTextMaskingRule( + "邮箱", + "[\\w.-]+@[\\w.-]+\\.\\w+", + RichTextMaskingRule.MaskType.PARTIAL, + null) + .setPreserveLeft(1) + .setPreserveRight(0) + .setMaskChar('*'); + } + + /** + * 创建一个网址脱敏规则 + * + * @param replacement 替换文本 + * @return 网址脱敏规则 + */ + public static RichTextMaskingRule createUrlRule(String replacement) { + return new RichTextMaskingRule( + "网址", + "https?://[\\w.-]+(?:/[\\w.-]*)*", + RichTextMaskingRule.MaskType.REPLACE, + replacement); + } + + /** + * 创建一个敏感词脱敏规则 + * + * @param pattern 敏感词正则表达式 + * @return 敏感词脱敏规则 + */ + public static RichTextMaskingRule createSensitiveWordRule(String pattern) { + return new RichTextMaskingRule( + "敏感词", + pattern, + RichTextMaskingRule.MaskType.FULL, + null) + .setMaskChar('*'); + } + + /** + * 创建一个自定义脱敏规则 + * + * @param name 规则名称 + * @param pattern 匹配模式(正则表达式) + * @param maskType 脱敏类型 + * @param replacement 替换内容 + * @return 自定义脱敏规则 + */ + public static RichTextMaskingRule createCustomRule(String name, String pattern, + RichTextMaskingRule.MaskType maskType, + String replacement) { + return new RichTextMaskingRule(name, pattern, maskType, replacement); + } + + /** + * 创建一个部分脱敏规则 + * + * @param name 规则名称 + * @param pattern 匹配模式(正则表达式) + * @param preserveLeft 保留左侧字符数 + * @param preserveRight 保留右侧字符数 + * @param maskChar 脱敏字符 + * @return 部分脱敏规则 + */ + public static RichTextMaskingRule createPartialMaskRule(String name, String pattern, + int preserveLeft, int preserveRight, + char maskChar) { + return new RichTextMaskingRule(name, pattern, preserveLeft, preserveRight, maskChar); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/masking/RichTextMaskingProcessor.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/masking/RichTextMaskingProcessor.java new file mode 100644 index 000000000..afdd0f016 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/masking/RichTextMaskingProcessor.java @@ -0,0 +1,312 @@ +package org.dromara.hutool.core.data.masking; + +import org.dromara.hutool.core.text.StrUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 富文本脱敏处理器,用于对富文本内容进行脱敏处理 + * + * @author xjf + */ +public class RichTextMaskingProcessor { + + /** + * 脱敏规则列表 + */ + private final List rules = new ArrayList<>(); + + /** + * 是否保留HTML标签 + */ + private boolean preserveHtmlTags = true; + + /** + * 默认的脱敏字符 + */ + private char defaultMaskChar = '*'; + + /** + * 构造函数 + */ + public RichTextMaskingProcessor() { + } + + /** + * 构造函数 + * + * @param preserveHtmlTags 是否保留HTML标签 + */ + public RichTextMaskingProcessor(boolean preserveHtmlTags) { + this.preserveHtmlTags = preserveHtmlTags; + } + + /** + * 添加脱敏规则 + * + * @param rule 脱敏规则 + * @return this + */ + public RichTextMaskingProcessor addRule(RichTextMaskingRule rule) { + this.rules.add(rule); + return this; + } + + /** + * 设置默认的脱敏字符 + * + * @param defaultMaskChar 默认的脱敏字符 + * @return this + */ + public RichTextMaskingProcessor setDefaultMaskChar(char defaultMaskChar) { + this.defaultMaskChar = defaultMaskChar; + return this; + } + + /** + * 对文本内容进行脱敏处理 + * + * @param text 文本内容 + * @return 脱敏后的文本 + */ + public String mask(String text) { + if (StrUtil.isBlank(text)) { + return text; + } + + // 如果是HTML内容,则需要特殊处理 + if (preserveHtmlTags && isHtmlContent(text)) { + return maskHtmlContent(text); + } else { + // 普通文本直接处理 + return maskPlainText(text); + } + } + + /** + * 判断是否为HTML内容 + * + * @param text 文本内容 + * @return 是否为HTML内容 + */ + private boolean isHtmlContent(String text) { + // 简单判断是否包含HTML标签 + return text.contains("<") && text.contains(">") && + (text.contains("")); + } + + /** + * 对HTML内容进行脱敏处理 + * + * @param html HTML内容 + * @return 脱敏后的HTML + */ + private String maskHtmlContent(String html) { + StringBuilder result = new StringBuilder(); + int lastIndex = 0; + boolean inTag = false; + String currentTag = null; + + for (int i = 0; i < html.length(); i++) { + char c = html.charAt(i); + + if (c == '<') { + // 处理标签前的文本内容 + if (!inTag && i > lastIndex) { + String textContent = html.substring(lastIndex, i); + result.append(processTextContentWithContext(textContent, currentTag)); + } + + inTag = true; + lastIndex = i; + + // 尝试获取当前标签名 + int tagNameStart = i + 1; + if (tagNameStart < html.length()) { + // 跳过结束标签的斜杠 + if (html.charAt(tagNameStart) == '/') { + tagNameStart++; + } + + // 查找标签名结束位置 + int tagNameEnd = html.indexOf(' ', tagNameStart); + if (tagNameEnd == -1) { + tagNameEnd = html.indexOf('>', tagNameStart); + } + + if (tagNameEnd > tagNameStart) { + currentTag = html.substring(tagNameStart, tagNameEnd).toLowerCase(); + } + } + } else if (c == '>' && inTag) { + inTag = false; + result.append(html, lastIndex, i + 1); // 保留标签 + lastIndex = i + 1; + } + } + + // 处理最后一部分 + if (lastIndex < html.length()) { + if (inTag) { + // 如果还在标签内,直接添加剩余部分 + result.append(html.substring(lastIndex)); + } else { + // 处理最后的文本内容 + String textContent = html.substring(lastIndex); + result.append(processTextContentWithContext(textContent, currentTag)); + } + } + + return result.toString(); + } + + /** + * 根据上下文处理文本内容 + * + * @param text 文本内容 + * @param tagName 当前所在的标签名 + * @return 处理后的文本 + */ + private String processTextContentWithContext(String text, String tagName) { + if (StrUtil.isBlank(text)) { + return text; + } + + String result = text; + + for (RichTextMaskingRule rule : rules) { + // 检查是否需要根据标签进行过滤 + if (tagName != null) { + // 如果设置了只包含特定标签且当前标签不在列表中,则跳过 + if (!rule.getIncludeTags().isEmpty() && !rule.getIncludeTags().contains(tagName)) { + continue; + } + + // 如果当前标签在排除列表中,则跳过 + if (rule.getExcludeTags().contains(tagName)) { + continue; + } + } + + // 应用脱敏规则 + result = applyMaskingRule(result, rule); + } + + return result; + } + + /** + * 对普通文本进行脱敏处理 + * + * @param text 文本内容 + * @return 脱敏后的文本 + */ + private String maskPlainText(String text) { + String result = text; + + for (RichTextMaskingRule rule : rules) { + result = applyMaskingRule(result, rule); + } + + return result; + } + + /** + * 应用脱敏规则 + * + * @param text 文本内容 + * @param rule 脱敏规则 + * @return 脱敏后的文本 + */ + private String applyMaskingRule(String text, RichTextMaskingRule rule) { + if (StrUtil.isBlank(text) || StrUtil.isBlank(rule.getPattern())) { + return text; + } + + Pattern pattern = Pattern.compile(rule.getPattern()); + Matcher matcher = pattern.matcher(text); + + StringBuffer sb = new StringBuffer(); + + while (matcher.find()) { + String matched = matcher.group(); + String replacement; + + switch (rule.getMaskType()) { + case FULL: + // 完全脱敏,用脱敏字符替换整个匹配内容 + replacement = StrUtil.repeat(rule.getMaskChar(), matched.length()); + break; + + case PARTIAL: + // 部分脱敏,保留部分原始内容 + replacement = partialMask(matched, rule.getPreserveLeft(), rule.getPreserveRight(), rule.getMaskChar()); + break; + + case REPLACE: + // 替换脱敏,用指定文本替换 + replacement = rule.getReplacement(); + break; + + default: + replacement = matched; + break; + } + + // 处理正则表达式中的特殊字符 + matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); + } + + matcher.appendTail(sb); + + return sb.toString(); + } + + /** + * 部分脱敏,保留部分原始内容 + * + * @param text 原文本 + * @param preserveLeft 保留左侧字符数 + * @param preserveRight 保留右侧字符数 + * @param maskChar 脱敏字符 + * @return 脱敏后的文本 + */ + private String partialMask(String text, int preserveLeft, int preserveRight, char maskChar) { + if (StrUtil.isBlank(text)) { + return text; + } + + int length = text.length(); + + // 调整保留字符数,确保不超过文本长度 + preserveLeft = Math.min(preserveLeft, length); + preserveRight = Math.min(preserveRight, length - preserveLeft); + + // 计算需要脱敏的字符数 + int maskLength = length - preserveLeft - preserveRight; + + if (maskLength <= 0) { + return text; + } + + StringBuilder sb = new StringBuilder(length); + + // 添加左侧保留的字符 + if (preserveLeft > 0) { + sb.append(text, 0, preserveLeft); + } + + // 添加脱敏字符 + sb.append(StrUtil.repeat(maskChar, maskLength)); + + // 添加右侧保留的字符 + if (preserveRight > 0) { + sb.append(text, length - preserveRight, length); + } + + return sb.toString(); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/masking/RichTextMaskingRule.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/masking/RichTextMaskingRule.java new file mode 100644 index 000000000..2ec006efd --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/masking/RichTextMaskingRule.java @@ -0,0 +1,223 @@ +package org.dromara.hutool.core.data.masking; + +import java.util.HashSet; +import java.util.Set; + +/** + * 富文本脱敏规则,用于配置如何对富文本内容进行脱敏处理 + * + * @author xjf + */ +public class RichTextMaskingRule { + + /** + * 脱敏类型枚举 + */ + public enum MaskType { + /** + * 完全脱敏,将匹配的内容完全替换为指定字符 + */ + FULL, + + /** + * 部分脱敏,保留部分原始内容 + */ + PARTIAL, + + /** + * 替换脱敏,将匹配的内容替换为指定的替换文本 + */ + REPLACE + } + + /** + * 规则名称 + */ + private String name; + + /** + * 匹配模式(正则表达式) + */ + private String pattern; + + /** + * 脱敏类型 + */ + private MaskType maskType; + + /** + * 替换内容 + */ + private String replacement; + + /** + * 保留左侧字符数(用于PARTIAL类型) + */ + private int preserveLeft; + + /** + * 保留右侧字符数(用于PARTIAL类型) + */ + private int preserveRight; + + /** + * 脱敏字符 + */ + private char maskChar = '*'; + + /** + * 是否处理HTML标签内容 + */ + private boolean processHtmlTags = false; + + /** + * 需要排除的HTML标签 + */ + private Set excludeTags = new HashSet<>(); + + /** + * 仅处理指定的HTML标签 + */ + private Set includeTags = new HashSet<>(); + + /** + * 构造函数 + */ + public RichTextMaskingRule() { + } + + /** + * 构造函数 + * + * @param name 规则名称 + * @param pattern 匹配模式(正则表达式) + * @param maskType 脱敏类型 + * @param replacement 替换内容 + */ + public RichTextMaskingRule(String name, String pattern, MaskType maskType, String replacement) { + this.name = name; + this.pattern = pattern; + this.maskType = maskType; + this.replacement = replacement; + } + + /** + * 构造函数,用于部分脱敏 + * + * @param name 规则名称 + * @param pattern 匹配模式(正则表达式) + * @param preserveLeft 保留左侧字符数 + * @param preserveRight 保留右侧字符数 + * @param maskChar 脱敏字符 + */ + public RichTextMaskingRule(String name, String pattern, int preserveLeft, int preserveRight, char maskChar) { + this.name = name; + this.pattern = pattern; + this.maskType = MaskType.PARTIAL; + this.preserveLeft = preserveLeft; + this.preserveRight = preserveRight; + this.maskChar = maskChar; + } + + // Getter and Setter methods + + public String getName() { + return name; + } + + public RichTextMaskingRule setName(String name) { + this.name = name; + return this; + } + + public String getPattern() { + return pattern; + } + + public RichTextMaskingRule setPattern(String pattern) { + this.pattern = pattern; + return this; + } + + public MaskType getMaskType() { + return maskType; + } + + public RichTextMaskingRule setMaskType(MaskType maskType) { + this.maskType = maskType; + return this; + } + + public String getReplacement() { + return replacement; + } + + public RichTextMaskingRule setReplacement(String replacement) { + this.replacement = replacement; + return this; + } + + public int getPreserveLeft() { + return preserveLeft; + } + + public RichTextMaskingRule setPreserveLeft(int preserveLeft) { + this.preserveLeft = preserveLeft; + return this; + } + + public int getPreserveRight() { + return preserveRight; + } + + public RichTextMaskingRule setPreserveRight(int preserveRight) { + this.preserveRight = preserveRight; + return this; + } + + public char getMaskChar() { + return maskChar; + } + + public RichTextMaskingRule setMaskChar(char maskChar) { + this.maskChar = maskChar; + return this; + } + + public boolean isProcessHtmlTags() { + return processHtmlTags; + } + + public RichTextMaskingRule setProcessHtmlTags(boolean processHtmlTags) { + this.processHtmlTags = processHtmlTags; + return this; + } + + public Set getExcludeTags() { + return excludeTags; + } + + public RichTextMaskingRule setExcludeTags(Set excludeTags) { + this.excludeTags = excludeTags; + return this; + } + + public RichTextMaskingRule addExcludeTag(String tag) { + this.excludeTags.add(tag.toLowerCase()); + return this; + } + + public Set getIncludeTags() { + return includeTags; + } + + public RichTextMaskingRule setIncludeTags(Set includeTags) { + this.includeTags = includeTags; + return this; + } + + public RichTextMaskingRule addIncludeTag(String tag) { + this.includeTags.add(tag.toLowerCase()); + return this; + } +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/data/RichTextMaskingUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/data/RichTextMaskingUtilTest.java new file mode 100644 index 000000000..7a08eb997 --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/data/RichTextMaskingUtilTest.java @@ -0,0 +1,202 @@ +package org.dromara.hutool.core.data; + +import org.dromara.hutool.core.data.masking.RichTextMaskingProcessor; +import org.dromara.hutool.core.data.masking.RichTextMaskingRule; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; + +/** + * 富文本脱敏工具类测试 + * + * @author xjf + */ +public class RichTextMaskingUtilTest { + + @Test + public void testDefaultMask() { + // 测试默认脱敏功能 + String html = "这是一封邮件,联系人:test@example.com,网址:https://www.example.com,包含机密信息。"; + String masked = RichTextMaskingUtil.mask(html); + + // 验证邮箱被脱敏 + Assertions.assertFalse(masked.contains("test@example.com")); + Assertions.assertTrue(masked.contains("t***")); + + // 验证网址被脱敏 + Assertions.assertFalse(masked.contains("https://www.example.com")); + Assertions.assertTrue(masked.contains("[网址已隐藏]")); + + // 验证敏感词被脱敏 + Assertions.assertFalse(masked.contains("机密")); + Assertions.assertTrue(masked.contains("**")); + } + + @Test + public void testHtmlContentMask() { + // 测试HTML内容脱敏 + String html = "

这是一封邮件,联系人:test@example.com," + + "网址:https://www.example.com," + + "包含机密信息。

"; + String masked = RichTextMaskingUtil.mask(html); + + // 验证HTML标签被保留 + Assertions.assertTrue(masked.contains("

")); + Assertions.assertTrue(masked.contains("

")); + Assertions.assertTrue(masked.contains("")); + + // 验证邮箱被脱敏 + Assertions.assertFalse(masked.contains("test@example.com")); + Assertions.assertTrue(masked.contains("t***")); + + // 验证网址被脱敏 + Assertions.assertFalse(masked.contains("https://www.example.com")); + Assertions.assertTrue(masked.contains("[网址已隐藏]")); + + // 验证敏感词被脱敏 + Assertions.assertFalse(masked.contains("机密")); + Assertions.assertTrue(masked.contains("**")); + } + + @Test + public void testCustomProcessor() { + // 创建自定义处理器 + RichTextMaskingProcessor processor = RichTextMaskingUtil.createProcessor(true); + + // 添加自定义规则 - 手机号码 + processor.addRule(RichTextMaskingUtil.createPartialMaskRule( + "手机号", + "1[3-9]\\d{9}", + 3, + 4, + '*')); + + // 添加自定义规则 - 公司名称 + processor.addRule(RichTextMaskingUtil.createCustomRule( + "公司名称", + "XX科技有限公司", + RichTextMaskingRule.MaskType.REPLACE, + "[公司名称已隐藏]")); + + // 测试文本 + String text = "联系电话:13812345678,公司名称:XX科技有限公司"; + String masked = RichTextMaskingUtil.mask(text, processor); + + // 验证手机号被脱敏 + Assertions.assertFalse(masked.contains("13812345678")); + Assertions.assertTrue(masked.contains("138*****5678")); + + // 验证公司名称被脱敏 + Assertions.assertFalse(masked.contains("XX科技有限公司")); + Assertions.assertTrue(masked.contains("[公司名称已隐藏]")); + } + + @Test + public void testTagFiltering() { + // 创建自定义处理器 + RichTextMaskingProcessor processor = RichTextMaskingUtil.createProcessor(true); + + // 创建只在特定标签中生效的规则 + RichTextMaskingRule rule = RichTextMaskingUtil.createCustomRule( + "标签内敏感信息", + "敏感信息", + RichTextMaskingRule.MaskType.REPLACE, + "[已隐藏]"); + + // 设置只在div标签中生效 + Set includeTags = new HashSet<>(); + includeTags.add("div"); + rule.setIncludeTags(includeTags); + + processor.addRule(rule); + + // 测试HTML + String html = "

这是一段敏感信息

这也是一段敏感信息
"; + String masked = RichTextMaskingUtil.mask(html, processor); + + // 验证只有div标签中的敏感信息被脱敏 + Assertions.assertTrue(masked.contains("

这是一段敏感信息

")); + Assertions.assertTrue(masked.contains("
这也是一段[已隐藏]
")); + } + + @Test + public void testExcludeTags() { + // 创建自定义处理器 + RichTextMaskingProcessor processor = RichTextMaskingUtil.createProcessor(true); + + // 创建排除特定标签的规则 + RichTextMaskingRule rule = RichTextMaskingUtil.createCustomRule( + "排除标签内敏感信息", + "敏感信息", + RichTextMaskingRule.MaskType.REPLACE, + "[已隐藏]"); + + // 设置排除code标签 + rule.addExcludeTag("code"); + + processor.addRule(rule); + + // 测试HTML + String html = "

这是一段敏感信息

这是代码中的敏感信息"; + String masked = RichTextMaskingUtil.mask(html, processor); + + // 验证code标签中的敏感信息不被脱敏 + Assertions.assertTrue(masked.contains("

这是一段[已隐藏]

")); + Assertions.assertTrue(masked.contains("这是代码中的敏感信息")); + } + + @Test + public void testComplexHtml() { + // 测试复杂HTML内容 + String html = "
" + + "

公司内部文档

" + + "

联系人:张三 zhangsan@example.com

" + + "

电话:13812345678

" + + "
这是一段机密信息,请勿外传
" + + "
// 这是一段代码\nString password = \"123456\";
" + + "

公司网址:https://www.example.com

" + + "
"; + + // 创建自定义处理器 + RichTextMaskingProcessor processor = RichTextMaskingUtil.createProcessor(true); + + // 添加邮箱脱敏规则 + processor.addRule(RichTextMaskingUtil.createEmailRule()); + + // 添加手机号脱敏规则 + processor.addRule(RichTextMaskingUtil.createPartialMaskRule( + "手机号", + "1[3-9]\\d{9}", + 3, + 4, + '*')); + + // 添加敏感词脱敏规则 + processor.addRule(RichTextMaskingUtil.createSensitiveWordRule("机密|内部")); + + // 添加网址脱敏规则 + processor.addRule(RichTextMaskingUtil.createUrlRule("[网址已隐藏]")); + + // 添加密码脱敏规则,但排除code标签 + RichTextMaskingRule passwordRule = RichTextMaskingUtil.createCustomRule( + "密码", + "password = \"[^\"]+\"", + RichTextMaskingRule.MaskType.REPLACE, + "password = \"******\""); + passwordRule.addExcludeTag("code"); + processor.addRule(passwordRule); + + String masked = RichTextMaskingUtil.mask(html, processor); + + // 验证结果 + Assertions.assertTrue(masked.contains("

公司**文档

")); + Assertions.assertTrue(masked.contains("z***")); + Assertions.assertTrue(masked.contains("138*****5678")); + Assertions.assertTrue(masked.contains("这是一段**信息")); + Assertions.assertTrue(masked.contains("String password = \"123456\"")); + Assertions.assertTrue(masked.contains("[网址已隐藏]")); + } +}