| | |
| | | if (body.getThreshold() != null) rule.setThreshold(body.getThreshold()); |
| | | if (body.getAlertLevel() != null) rule.setAlertLevel(body.getAlertLevel()); |
| | | if (body.getDescription() != null) rule.setDescription(body.getDescription()); |
| | | // 阈值变化时同步规则名称与说明(名称/说明里的 ±N% 按新阈值改写),并随保存持久化 |
| | | // 阈值变化时同步规则名称与说明(名称/说明里与旧阈值对应的 N% 片段按新阈值改写),并随保存持久化 |
| | | if (body.getThreshold() != null && !body.getThreshold().equals(oldThreshold)) { |
| | | syncThresholdText(rule); |
| | | syncThresholdText(rule, oldThreshold); |
| | | } |
| | | rule.setUpdatedAt(LocalDateTime.now()); |
| | | ruleMapper.updateById(rule); |
| | | return Result.ok("已保存"); |
| | | } |
| | | |
| | | /** ±N% 字样匹配(名称/说明中随阈值动态变化的片段) */ |
| | | private static final Pattern PCT_TOKEN = Pattern.compile("±\\s*\\d+(?:\\.\\d+)?\\s*%"); |
| | | /** % 前的数值(名称/说明中随阈值动态变化的片段;% 前可能是 ±、>、超过、高出 等描述词,仅替换数字本身) */ |
| | | private static final Pattern PCT_NUM = Pattern.compile("\\d+(?:\\.\\d+)?(?=%)"); |
| | | |
| | | /** 把文本中的 ±N% 替换为 ±threshold%(含已带 ± 的前缀,避免双 ±) */ |
| | | private String decorateThresholdText(String text, String threshold) { |
| | | if (text == null || text.trim().isEmpty() || threshold == null || threshold.trim().isEmpty()) return text; |
| | | String token = "±" + threshold.trim() + "%"; |
| | | return PCT_TOKEN.matcher(text).replaceAll(Matcher.quoteReplacement(token)); |
| | | /** 解析阈值字符串中第一个数值;无法解析返回 null */ |
| | | private Double parseThresholdNum(String threshold) { |
| | | if (threshold == null || threshold.trim().isEmpty()) return null; |
| | | java.util.regex.Matcher m = Pattern.compile("\\d+(?:\\.\\d+)?").matcher(threshold.trim()); |
| | | if (!m.find()) return null; |
| | | try { |
| | | return Double.parseDouble(m.group()); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** 名称/说明随阈值同步(读列表时也动态校正,DB 中旧文本不阻塞显示) */ |
| | | private void syncThresholdText(AuditRule rule) { |
| | | /** 数值展示:整数去掉小数位,其余保留原精度 */ |
| | | private String fmtNum(double v) { |
| | | long l = Math.round(v); |
| | | if (Math.abs(v - l) < 1e-9) return Long.toString(l); |
| | | return new java.math.BigDecimal(String.valueOf(v)).stripTrailingZeros().toPlainString(); |
| | | } |
| | | |
| | | /** 由阈值数值推导“说明文字里写的是百分之几”的候选口径: |
| | | * ① 阈值本身就是百分数(如 20 → ±20%); |
| | | * ② 阈值是小数比例(如 0.02 → 2%、0.5 → 50%);此时 <1 才启用; |
| | | * ③ 阈值是比值(如 1.3 → 高出30%);此时 >1 才启用。 */ |
| | | private double[] thresholdPercentCandidates(double v) { |
| | | double[] cands; |
| | | if (v > 0 && v < 1) { |
| | | cands = new double[]{v, v * 100}; |
| | | } else if (v > 1) { |
| | | cands = new double[]{v, (v - 1) * 100}; |
| | | } else { |
| | | cands = new double[]{v}; |
| | | } |
| | | return cands; |
| | | } |
| | | |
| | | /** 按旧阈值匹配文本中已有的百分比片段,仅替换“与旧阈值对得上”的数字为新阈值对应数字;对不上的数字(如说明里与阈值无关的数值)保持不动 */ |
| | | private String decorateThresholdText(String text, String oldThreshold, String newThreshold) { |
| | | if (text == null || text.trim().isEmpty()) return text; |
| | | Double oldNum = parseThresholdNum(oldThreshold); |
| | | Double newNum = parseThresholdNum(newThreshold); |
| | | if (oldNum == null || newNum == null) return text; |
| | | double[] oldCands = thresholdPercentCandidates(oldNum); |
| | | double[] newCands = thresholdPercentCandidates(newNum); |
| | | if (oldCands.length != newCands.length) return text; |
| | | java.util.regex.Matcher m = PCT_NUM.matcher(text); |
| | | StringBuffer sb = new StringBuffer(); |
| | | while (m.find()) { |
| | | double token; |
| | | try { |
| | | token = Double.parseDouble(m.group()); |
| | | } catch (Exception e) { |
| | | continue; |
| | | } |
| | | String repl = null; |
| | | for (int i = 0; i < oldCands.length; i++) { |
| | | if (Math.abs(oldCands[i] - token) < 1e-9) { |
| | | repl = fmtNum(newCands[i]); |
| | | break; |
| | | } |
| | | } |
| | | if (repl == null) continue; |
| | | m.appendReplacement(sb, Matcher.quoteReplacement(repl)); |
| | | } |
| | | m.appendTail(sb); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** 名称/说明随阈值同步(update 时按 旧→新 改写并持久化) */ |
| | | private void syncThresholdText(AuditRule rule, String oldThreshold) { |
| | | if (rule == null || rule.getThreshold() == null) return; |
| | | rule.setRuleName(decorateThresholdText(rule.getRuleName(), rule.getThreshold())); |
| | | rule.setDescription(decorateThresholdText(rule.getDescription(), rule.getThreshold())); |
| | | rule.setRuleName(decorateThresholdText(rule.getRuleName(), oldThreshold, rule.getThreshold())); |
| | | rule.setDescription(decorateThresholdText(rule.getDescription(), oldThreshold, rule.getThreshold())); |
| | | } |
| | | |
| | | /** 读列表时自愈:仅当文本片段与当前阈值对应时才改写(幂等,不误伤与阈值无关的数字) */ |
| | | private void syncThresholdText(AuditRule rule) { |
| | | syncThresholdText(rule, rule == null ? null : rule.getThreshold()); |
| | | } |
| | | } |