From 175fbf93a2166aceb7aba1b4efeee1de3213163c Mon Sep 17 00:00:00 2001 From: yuejiajun <1530620364@qq.com> Date: Sun, 28 Sep 2025 12:01:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=B9=E6=B3=95=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E4=B8=BA=20{{key}}=20=E6=A0=BC=E5=BC=8F=E7=9A=84=20js?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../demo/draft/demo043/util/FormatUtil.java | 447 ++++++++++++++++-- 1 file changed, 407 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/example/demo/draft/demo043/util/FormatUtil.java b/src/main/java/com/example/demo/draft/demo043/util/FormatUtil.java index 6bffe1d..186bdb3 100644 --- a/src/main/java/com/example/demo/draft/demo043/util/FormatUtil.java +++ b/src/main/java/com/example/demo/draft/demo043/util/FormatUtil.java @@ -1,8 +1,14 @@ package com.example.demo.draft.demo043.util; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -72,6 +78,7 @@ import java.util.regex.Pattern; *

创建时间: 2024年

*

维护者: demo043项目组

*/ +@Slf4j public class FormatUtil { /** @@ -1371,17 +1378,234 @@ public class FormatUtil { } /** - * 偷梁换柱 - 支持正则表达式和多种替换选项 - * @param source 源字符串 - * @param replacementMap 替换映射关系 - * @return 替换后的字符串 + * 字符串批量替换工具方法 + * + *

该方法提供强大的字符串替换功能,支持正则表达式和普通字符串替换, + * 可灵活控制大小写敏感和整词匹配等选项。方法名"偷梁换柱"形象地描述了替换功能。

+ * + *

功能特性:

+ * + * + * @param source 源字符串,如果为null则直接返回null + * @param replacementMap 替换映射表,Key为要查找的模式(正则表达式或普通字符串), + * Value为替换内容。如果为null或空则返回原字符串 + * @param caseSensitive 是否大小写敏感 + * true: 区分大小写,false: 不区分大小写 + * @param wholeWord 是否整词匹配 + * true: 只匹配完整单词,false: 匹配任意位置 + * @param useRegex 是否使用正则表达式 + * true: 将key作为正则表达式处理,false: 作为普通字符串处理 + * @return 替换后的字符串,如果源字符串或替换映射表为null/空则返回原字符串 + * @throws IllegalArgumentException 当replacementMap包含null键或值时抛出 */ - public static String stealBeamsAndReplacePillars(String source, Map replacementMap) { - return stealBeamsAndReplacePillars(source, replacementMap, false, false); + public static String stealBeamsAndReplacePillars(String source, Map replacementMap, + boolean caseSensitive, boolean wholeWord, boolean useRegex) { + // 参数校验:如果源字符串或替换映射表为空,直接返回原字符串 + if (source == null || source.isEmpty()) { + log.debug("源字符串为空,直接返回"); + return source; + } + + if (replacementMap == null || replacementMap.isEmpty()) { + log.debug("替换映射表为空,直接返回原字符串"); + return source; + } + + // 检查替换映射表中是否包含null键或值 + for (Map.Entry entry : replacementMap.entrySet()) { + if (entry.getKey() == null) { + throw new IllegalArgumentException("替换映射表中不能包含null键"); + } + if (entry.getValue() == null) { + log.warn("替换映射表中键 '{}' 的值为null,将作为空字符串处理", entry.getKey()); + } + } + + log.info("开始字符串替换处理,源字符串长度: {}, 替换规则数量: {}, 大小写敏感: {}, 整词匹配: {}, 使用正则: {}", + source.length(), replacementMap.size(), match(caseSensitive), match(wholeWord), match(useRegex)); + + String result = source; + int totalReplacements = 0; + + // 遍历替换映射表,按顺序应用每个替换规则 + for (Map.Entry entry : replacementMap.entrySet()) { + String patternStr = entry.getKey(); + String replacement = entry.getValue() != null ? entry.getValue() : ""; + + try { + if (useRegex) { + // 使用正则表达式替换 + log.info("应用替换规则(正则表达式替换): '{}' -> '{}'", patternStr, replacement); + result = applyRegexReplacement(result, patternStr, replacement, caseSensitive, wholeWord); + } else { + // 使用普通字符串替换 + log.info("应用替换规则(普通字符串替换): '{}' -> '{}'", patternStr, replacement); + result = applyLiteralReplacement(result, patternStr, replacement, caseSensitive, wholeWord); + } + + // 统计替换次数(通过比较字符串长度变化估算) + if (!result.equals(source)) { + totalReplacements++; + } + + } catch (Exception e) { + log.error("替换规则处理失败 - 模式: '{}', 替换值: '{}', 错误: {}", + patternStr, replacement, e.getMessage()); + // 发生错误时继续处理下一个规则,不中断整个流程 + } + } + + log.info("字符串替换完成,总应用规则数: {}, 实际产生变化的规则数: {}", + replacementMap.size(), totalReplacements); + + return result; } /** - * 偷梁换柱 - 支持正则表达式和多种替换选项 + * 应用正则表达式替换 + * + * @param source 源字符串 + * @param regex 正则表达式模式 + * @param replacement 替换内容 + * @param caseSensitive 是否大小写敏感 + * @param wholeWord 是否整词匹配 + * @return 替换后的字符串 + */ + private static String applyRegexReplacement(String source, String regex, String replacement, + boolean caseSensitive, boolean wholeWord) { + String processedRegex = regex; + + // 处理整词匹配:如果启用整词匹配且正则表达式尚未包含单词边界 + if (wholeWord && !processedRegex.startsWith("\\b") && !processedRegex.contains("\\b")) { + processedRegex = "\\b" + processedRegex + "\\b"; + log.info("添加单词边界,处理后的正则: {}", processedRegex); // trace + } + + try { + // 设置正则表达式标志 + int flags = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE; + Pattern pattern = Pattern.compile(processedRegex, flags); + Matcher matcher = pattern.matcher(source); + + String result = matcher.replaceAll(replacement); + + // 记录替换详情(调试级别) + if (log.isTraceEnabled() && !result.equals(source)) { + int count = 0; + matcher.reset(); + while (matcher.find()) count++; + log.info("正则替换完成: '{}' 匹配 {} 次", regex, count); // trace + } + + return result; + + } catch (Exception e) { + log.warn("正则表达式编译或执行失败,降级为普通字符串替换 - 模式: '{}', 错误: {}", + regex, e.getMessage()); + // 降级处理:使用普通字符串替换 + return applyLiteralReplacement(source, regex, replacement, caseSensitive, wholeWord); + } + } + + /** + * 应用普通字符串替换 + * + * @param source 源字符串 + * @param literal 要查找的字符串 + * @param replacement 替换内容 + * @param caseSensitive 是否大小写敏感 + * @param wholeWord 是否整词匹配 + * @return 替换后的字符串 + */ + private static String applyLiteralReplacement(String source, String literal, String replacement, + boolean caseSensitive, boolean wholeWord) { + if (wholeWord) { + // 整词匹配处理:使用正则表达式实现单词边界匹配 + try { + String regex = "\\b" + Pattern.quote(literal) + "\\b"; + int flags = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE; + Pattern pattern = Pattern.compile(regex, flags); + return pattern.matcher(source).replaceAll(replacement); + } catch (Exception e) { + log.warn("整词匹配处理失败,使用普通替换 - 模式: '{}'", literal); + // 降级为普通替换 + } + } + + // 普通字符串替换 + if (caseSensitive) { + // 大小写敏感替换 + return source.replace(literal, replacement); + } else { + // 大小写不敏感替换(需要手动实现) + return replaceIgnoreCase(source, literal, replacement); + } + } + + /** + * 不区分大小写的字符串替换 + * + * @param source 源字符串 + * @param target 要查找的字符串 + * @param replacement 替换内容 + * @return 替换后的字符串 + */ + private static String replaceIgnoreCase(String source, String target, String replacement) { + if (source == null || target == null || replacement == null) { + return source; + } + + StringBuilder result = new StringBuilder(); + String lowerSource = source.toLowerCase(); + String lowerTarget = target.toLowerCase(); + int index = 0; + int targetLength = target.length(); + int count = 0; + + while (true) { + int pos = lowerSource.indexOf(lowerTarget, index); + if (pos == -1) break; + + // 添加不匹配部分 + result.append(source, index, pos); + // 添加替换内容 + result.append(replacement); + + index = pos + targetLength; + count++; + } + + // 添加剩余部分 + result.append(source.substring(index)); + + if (count > 0) { + log.info("不区分大小写替换完成: '{}' 匹配 {} 次", target, count); // trace + } + + return result.toString(); + } + + /** + * 偷梁换柱 - 使用默认参数(使用正则) + * @param source 源字符串 + * @param replacementMap 替换映射关系 + * @param caseSensitive 是否区分大小写 + * @param wholeWord 是否整词匹配 + * @return 替换后的字符串 + */ + public static String stealBeamsAndReplacePillars(String source, Map replacementMap, + boolean caseSensitive, boolean wholeWord) { + return stealBeamsAndReplacePillars(source, replacementMap, caseSensitive, wholeWord, true); + } + + /** + * 偷梁换柱 - 使用默认参数(非整词匹配、使用正则) * @param source 源字符串 * @param replacementMap 替换映射关系 * @param caseSensitive 是否区分大小写 @@ -1392,41 +1616,184 @@ public class FormatUtil { } /** - * 偷梁换柱 + * 偷梁换柱 - 使用默认参数(大小写不敏感、非整词匹配、使用正则) + * * @param source 源字符串 * @param replacementMap 替换映射关系 - * @param caseSensitive 是否区分大小写 - * @param wholeWord 是否整词匹配 * @return 替换后的字符串 */ - public static String stealBeamsAndReplacePillars(String source, Map replacementMap, - boolean caseSensitive, boolean wholeWord) { - if (source == null || replacementMap == null || replacementMap.isEmpty()) { - return source; - } - - String result = source; - - for (Map.Entry entry : replacementMap.entrySet()) { - String regex = entry.getKey(); - String replacement = entry.getValue(); - - // 处理整词匹配 - if (wholeWord && !regex.startsWith("\\b")) { - regex = "\\b" + regex + "\\b"; - } - - try { - int flags = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE; - Pattern pattern = Pattern.compile(regex, flags); - Matcher matcher = pattern.matcher(result); - result = matcher.replaceAll(replacement); - } catch (Exception e) { - System.err.println("正则表达式错误: " + regex + " - " + e.getMessage()); - } - } - - return result; + public static String stealBeamsAndReplacePillars(String source, Map replacementMap) { + return stealBeamsAndReplacePillars(source, replacementMap, false, false, true); } + // ========== JSON对象再次加工 ========== + + /** + * 加载并预处理JSON文件 + * + *

该方法读取JSON文件内容,解析为对象列表,并对所有键名进行标准化处理, + * 将键名转换为双花括号格式({{key}})。主要用于模板处理或数据转换场景。

+ * + *

处理流程:

+ *
    + *
  1. 读取JSON文件内容为字符串
  2. + *
  3. 使用FastJSON库解析为对象列表
  4. + *
  5. 递归处理所有对象的键名,转换为{{key}}格式
  6. + *
+ * + * @param processJSONFile JSON文件路径,不能为null或空字符串 + * @return 包含预处理后对象的列表,列表中的每个对象的所有键名都已转换为{{key}}格式 + * @throws IOException 当文件读取失败、文件不存在或文件格式错误时抛出 + * @throws IllegalArgumentException 当processJSONFile为null或空字符串时抛出 + */ + public static List loadJSON(String processJSONFile) throws IOException { + // 参数校验 + if (processJSONFile == null || processJSONFile.trim().isEmpty()) { + throw new IllegalArgumentException("JSON文件路径不能为空或null"); + } + + log.info("开始加载JSON文件: {}", processJSONFile); + + try { + // Step 1: 读取JSON文件内容 + log.debug("读取文件内容..."); + String targetContent = Files.readString(Path.of(processJSONFile), StandardCharsets.UTF_8); + + if (targetContent == null || targetContent.trim().isEmpty()) { + log.warn("JSON文件内容为空: {}", processJSONFile); + return new ArrayList<>(); + } + + log.debug("文件内容读取成功,长度: {} 字符", targetContent.length()); + + // Step 2: 解析JSON字符串为对象列表 + log.debug("解析JSON内容..."); + List list = JSON.parseArray(targetContent); + + if (list == null) { + log.warn("JSON解析结果为null,返回空列表"); + return new ArrayList<>(); + } + + log.debug("JSON解析成功,对象数量: {}", list.size()); + + // Step 3: 处理所有对象的键名,转换为{{key}}格式 + log.debug("开始处理键名格式转换..."); + List processedList = processKeysToMustacheFormat(list); + + log.info("JSON文件加载完成,共处理 {} 个对象", processedList.size()); + return processedList; + + } catch (IOException e) { + log.error("读取JSON文件失败: {}", processJSONFile, e); + throw e; + } catch (Exception e) { + log.error("解析JSON内容失败: {}", processJSONFile, e); + throw new IOException("JSON解析失败: " + e.getMessage(), e); + } + } + + /** + * 递归处理对象中的所有键名,转换为双花括号格式 + * + *

该方法会遍历对象的所有层级,将每个键名从"key"格式转换为"{{key}}"格式

+ * + * @param originalList 原始对象列表 + * @return 键名转换后的新对象列表 + */ + private static List processKeysToMustacheFormat(List originalList) { + if (originalList == null || originalList.isEmpty()) { + return originalList; + } + + List processedList = new ArrayList<>(originalList.size()); + + for (int i = 0; i < originalList.size(); i++) { + try { + Object item = originalList.get(i); + Object processedItem = processSingleObject(item); + processedList.add(processedItem); + + if (log.isDebugEnabled() && i % 100 == 0) { + log.debug("已处理 {} 个对象", i); + } + } catch (Exception e) { + log.warn("处理第 {} 个对象时发生异常,跳过该对象", i, e); + // 发生异常时保留原始对象 + processedList.add(originalList.get(i)); + } + } + + return processedList; + } + + /** + * 处理单个对象的键名转换 + * + * @param obj 要处理的对象,支持Map、List和基本类型 + * @return 键名转换后的新对象 + */ + private static Object processSingleObject(Object obj) { + if (obj instanceof Map) { + // 处理Map对象 + @SuppressWarnings("unchecked") + Map originalMap = (Map) obj; + Map processedMap = new HashMap<>(originalMap.size()); + + for (Map.Entry entry : originalMap.entrySet()) { + String originalKey = entry.getKey(); + Object value = entry.getValue(); + + // 转换键名格式:key -> {{key}} + String processedKey = convertKeyToMustacheFormat(originalKey); + // 递归处理值 + Object processedValue = processSingleObject(value); + + processedMap.put(processedKey, processedValue); + } + return processedMap; + + } else if (obj instanceof List) { + // 处理List对象 + @SuppressWarnings("unchecked") + List originalList = (List) obj; + List processedList = new ArrayList<>(originalList.size()); + + for (Object item : originalList) { + processedList.add(processSingleObject(item)); + } + return processedList; + + } else { + // 基本类型(String、Number、Boolean等)直接返回 + return obj; + } + } + + /** + * 将键名转换为双花括号格式 + * + *

如果键名已经包含花括号,则不再重复添加

+ * + * @param key 原始键名 + * @return 转换后的键名,格式为{{key}} + */ + private static String convertKeyToMustacheFormat(String key) { + if (key == null || key.trim().isEmpty()) { + log.warn("遇到空键名,返回原始值"); + return key; + } + + // 如果键名已经是{{key}}格式,则不再处理 + if (key.startsWith("{{") && key.endsWith("}}")) { + return key; + } + + // 转换为{{key}}格式 + return "{{" + key + "}}"; + } + + private static String match(boolean flag) { + return flag ? "启用" : "禁用"; + } } \ No newline at end of file