package cn.iocoder.yudao.module.qcreport.engine; import java.math.BigDecimal; import java.time.temporal.TemporalAccessor; import java.time.format.DateTimeFormatter; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 上下文取值。 *

* 只做白名单式的属性导航,不解析、不执行任何代码: * 支持 a.b.c 与 a.b[0].c 两种写法,路径在数组上继续取属性时按元素逐个取值(pluck)。 *

* 与前端 {@code src/components/quality/engine/path.ts} 一一对应,两边必须同语义。 */ public final class Paths { /** 路径片段:普通属性名或数组下标 */ private static final Pattern TOKEN_PATTERN = Pattern.compile("[^.\\[\\]]+"); /** 禁止访问的属性名,避免顺着原型链读到构造器 */ private static final Set BLOCKED_KEYS = Set.of("__proto__", "constructor", "prototype"); private static final Pattern INTEGER_TEXT = Pattern.compile("^\\d+$"); private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private Paths() { } /** * 把路径拆成片段:inspectionItems[0].actualValue → [inspectionItems, 0, actualValue] */ public static List parsePath(String path) { List tokens = new ArrayList<>(); if (path == null) { return tokens; } Matcher matcher = TOKEN_PATTERN.matcher(path); while (matcher.find()) { tokens.add(matcher.group()); } return tokens; } /** * 按路径取值,取不到返回 null。 */ public static Object readPath(Object source, String path) { return readTokens(source, parsePath(path), 0); } private static Object readTokens(Object source, List tokens, int start) { Object current = source; for (int index = start; index < tokens.size(); index++) { String token = tokens.get(index); if (current == null) { return null; } if (current instanceof List list) { Integer arrayIndex = toIndex(token); if (arrayIndex == null) { // 数组上继续取属性:逐元素取值,得到同长度的数组 List plucked = new ArrayList<>(list.size()); for (Object element : list) { plucked.add(readTokens(element, tokens, index)); } return plucked; } current = arrayIndex < list.size() ? list.get(arrayIndex) : null; continue; } if (current instanceof Map map) { if (BLOCKED_KEYS.contains(token)) { return null; } current = map.get(token); continue; } // 标量后面还有路径片段,说明路径写错了 return null; } return current; } private static Integer toIndex(String token) { return INTEGER_TEXT.matcher(token).matches() ? Integer.valueOf(token) : null; } /** * 展示用格式化:空值渲染为空字符串,布尔值转中文,日期只保留到秒。 *

* 整数值的双精度数(如 60.0)按整数输出,与 JS 的 {@code String(60)} 保持一致, * 否则报告上会出现「标准值 60.0」这种不该有的尾数。 */ public static String formatValue(Object value) { if (value == null) { return ""; } if (value instanceof Boolean bool) { return bool ? "是" : "否"; } if (value instanceof Date date) { return DATE_TIME_FORMAT.format(date.toInstant().atZone(ZoneId.systemDefault())); } if (value instanceof TemporalAccessor temporal) { return DATE_TIME_FORMAT.format(temporal); } if (value instanceof Collection collection) { List parts = new ArrayList<>(collection.size()); for (Object element : collection) { parts.add(formatValue(element)); } return String.join("、", parts); } return formatNumberAware(value); } private static String formatNumberAware(Object value) { if (value instanceof Double || value instanceof Float || value instanceof BigDecimal) { return Numbers.toString(((Number) value).doubleValue()); } return String.valueOf(value); } /** * 数值化:非数值返回 NaN,由调用方决定如何处理。 */ public static double toNumber(Object value) { if (value instanceof Number number) { return number.doubleValue(); } if (value instanceof Boolean bool) { return bool ? 1D : 0D; } if (value instanceof String text && !text.isBlank()) { try { return Double.parseDouble(text.trim()); } catch (NumberFormatException ignored) { return Double.NaN; } } return Double.NaN; } /** * 把任意取值收敛成数值数组,供统计函数使用。 */ public static List toNumberArray(Object value) { List numbers = new ArrayList<>(); if (value instanceof Collection collection) { flattenInto(collection, numbers); return numbers; } double single = toNumber(value); if (Double.isFinite(single)) { numbers.add(single); } return numbers; } private static void flattenInto(Object value, List target) { if (value instanceof Collection collection) { for (Object element : collection) { flattenInto(element, target); } return; } double numeric = toNumber(value); if (Double.isFinite(numeric)) { target.add(numeric); } } /** * 把任意取值按一层一层摊平成列表,保留原始元素(不做数值转换),供 PASS_RATE 这类函数使用。 */ public static List flatten(Object value) { List result = new ArrayList<>(); if (value instanceof Collection collection) { for (Object element : collection) { result.addAll(flatten(element)); } return result; } result.add(value); return result; } }