package com.trafficaudit.rulemanage.controller;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.trafficaudit.common.Result;
|
import com.trafficaudit.rulemanage.entity.AuditRule;
|
import com.trafficaudit.rulemanage.mapper.AuditRuleMapper;
|
import org.springframework.web.bind.annotation.*;
|
|
import javax.annotation.Resource;
|
import java.time.LocalDateTime;
|
import java.util.regex.Pattern;
|
import java.util.regex.Matcher;
|
import java.util.List;
|
|
/** 规则管理:按报表类型查看/启停/调整阈值 */
|
@RestController
|
@RequestMapping("/api/rules")
|
public class RuleManageController {
|
|
@Resource
|
private AuditRuleMapper ruleMapper;
|
|
/** 规则列表,可按报表类型过滤 */
|
@GetMapping("/list")
|
public Result<List<AuditRule>> list(@RequestParam(value = "reportType", required = false) String reportType) {
|
LambdaQueryWrapper<AuditRule> qw = new LambdaQueryWrapper<>();
|
if (reportType != null && !reportType.trim().isEmpty()) {
|
qw.eq(AuditRule::getReportType, reportType.trim());
|
}
|
qw.orderByAsc(AuditRule::getSortOrder);
|
List<AuditRule> list = ruleMapper.selectList(qw);
|
for (AuditRule r : list) syncThresholdText(r); // 名称/说明随阈值动态展示(如 ±10% → ±20%)
|
return Result.ok(list);
|
}
|
|
/** 启停规则 */
|
@PostMapping("/toggle")
|
public Result<String> toggle(@RequestParam("id") Long id,
|
@RequestParam("enabled") Integer enabled) {
|
AuditRule rule = ruleMapper.selectById(id);
|
if (rule == null) return Result.error("规则不存在");
|
rule.setIsEnabled(enabled == null || enabled == 0 ? 0 : 1);
|
rule.setUpdatedAt(LocalDateTime.now());
|
ruleMapper.updateById(rule);
|
return Result.ok("已更新");
|
}
|
|
/** 更新阈值/等级/描述 */
|
@PostMapping("/update")
|
public Result<String> update(@RequestBody AuditRule body) {
|
if (body.getId() == null) return Result.error("缺少规则ID");
|
AuditRule rule = ruleMapper.selectById(body.getId());
|
if (rule == null) return Result.error("规则不存在");
|
String oldThreshold = rule.getThreshold();
|
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% 片段按新阈值改写),并随保存持久化
|
if (body.getThreshold() != null && !body.getThreshold().equals(oldThreshold)) {
|
syncThresholdText(rule, oldThreshold);
|
}
|
rule.setUpdatedAt(LocalDateTime.now());
|
ruleMapper.updateById(rule);
|
return Result.ok("已保存");
|
}
|
|
/** 名称/说明中随阈值动态变化的数字片段:匹配任意数字(% 前后、台/吨/公里等单位前均可) */
|
private static final Pattern NUM_TOKEN = Pattern.compile("\\d+(?:\\.\\d+)?");
|
|
/** 解析阈值字符串中的所有数值(支持“5,5,50”等多段阈值,如 RULE_001 分项运政对比) */
|
private java.util.List<Double> parseThresholdNums(String threshold) {
|
java.util.List<Double> out = new java.util.ArrayList<>();
|
if (threshold == null || threshold.trim().isEmpty()) return out;
|
java.util.regex.Matcher m = NUM_TOKEN.matcher(threshold.trim());
|
while (m.find()) {
|
try {
|
out.add(Double.parseDouble(m.group()));
|
} catch (Exception e) {
|
// 忽略无法解析的段
|
}
|
}
|
return out;
|
}
|
|
/** 数值展示:整数去掉小数位,其余保留原精度 */
|
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;
|
java.util.List<Double> olds = parseThresholdNums(oldThreshold);
|
java.util.List<Double> news = parseThresholdNums(newThreshold);
|
if (olds.isEmpty() || olds.size() != news.size()) return text;
|
int n = olds.size();
|
double[][] oc = new double[n][];
|
double[][] nc = new double[n][];
|
for (int i = 0; i < n; i++) {
|
oc[i] = thresholdPercentCandidates(olds.get(i));
|
nc[i] = thresholdPercentCandidates(news.get(i));
|
if (oc[i].length != nc[i].length) return text;
|
}
|
java.util.regex.Matcher m = NUM_TOKEN.matcher(text);
|
StringBuffer sb = new StringBuffer();
|
while (m.find()) {
|
double token;
|
try {
|
token = Double.parseDouble(m.group());
|
} catch (Exception e) {
|
continue;
|
}
|
String repl = null;
|
outer:
|
for (int i = 0; i < n; i++) {
|
for (int j = 0; j < oc[i].length; j++) {
|
if (Math.abs(oc[i][j] - token) < 1e-9) {
|
repl = fmtNum(nc[i][j]);
|
break outer;
|
}
|
}
|
}
|
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(), oldThreshold, rule.getThreshold()));
|
rule.setDescription(decorateThresholdText(rule.getDescription(), oldThreshold, rule.getThreshold()));
|
}
|
|
/** 读列表时自愈:仅当文本片段与当前阈值对应时才改写(幂等,不误伤与阈值无关的数字) */
|
private void syncThresholdText(AuditRule rule) {
|
syncThresholdText(rule, rule == null ? null : rule.getThreshold());
|
}
|
}
|