Browse Source

自定义持续时间规则开发,支持公式编辑和自定义变量

Gaokun Wang 5 months ago
parent
commit
769a5ccb9b

+ 1 - 1
als-modules/agile-assurance/src/main/java/org/eco/als/controller/AlgorithmController.java

@@ -150,7 +150,7 @@ public class AlgorithmController {
         String express = expressBo.getExpression();
         Map<String, Object> variables = expressBo.getVariables();
         // 执行表达式求值
-        Object result = formulaService.evaluateExpression(express, variables);
+        Object result = formulaService.evaluateExpression(expressBo);
         return CommonResult.success(result);
     }
 }

+ 19 - 0
als-modules/agile-assurance/src/main/java/org/eco/als/domain/bo/ExpressBo.java

@@ -4,8 +4,27 @@ import lombok.Data;
 
 import java.util.Map;
 
+/**
+ * @author wanggaokun
+ */
 @Data
 public class ExpressBo {
+    /**
+     * 表达式
+     */
     private String expression;
+    /**
+     * 数据Id
+     */
+    private Long ossId;
+
+    /**
+     * 数据列
+     */
+    private String[] columnData;
+
+    /**
+     * 自定义变量
+     */
     private Map<String, Object> variables;
 }

+ 2 - 0
als-modules/agile-assurance/src/main/java/org/eco/als/service/IAlgorithmService.java

@@ -1,5 +1,6 @@
 package org.eco.als.service;
 
+import cn.hutool.json.JSONArray;
 import org.eco.als.domain.bo.AlgorithmBo;
 import org.eco.als.domain.bo.QaBo;
 import org.eco.als.domain.bo.TaskBo;
@@ -24,6 +25,7 @@ public interface IAlgorithmService {
     List<String> getDataHeader(Long ossId);
 
     String getDataByOssId(Long ossId);
+    JSONArray getArrayByOssId(Long ossId);
 
     String executeQa(QaBo qaBo);
 }

+ 5 - 2
als-modules/agile-assurance/src/main/java/org/eco/als/service/IFormulaService.java

@@ -1,7 +1,10 @@
 package org.eco.als.service;
 
-import java.util.Map;
+import org.eco.als.domain.bo.ExpressBo;
 
+/**
+ * @author wanggaokun
+ */
 public interface IFormulaService {
-    Object evaluateExpression(String expression, Map<String, Object> variables) throws Exception;
+    Object evaluateExpression(ExpressBo expressBo) throws Exception;
 }

+ 7 - 0
als-modules/agile-assurance/src/main/java/org/eco/als/service/impl/AlgorithmService.java

@@ -366,6 +366,13 @@ public class AlgorithmService implements IAlgorithmService {
         String path = StringUtils.substringAfter(ossVo.getFileName(), Constants.RESOURCE_PREFIX);
         return CsvUtils.fileCsvToJson(path).toString();
     }
+    @Override
+    public JSONArray getArrayByOssId(Long ossId) {
+        SysOssVo ossVo = ossService.getById(ossId);
+        // 数据库资源地址
+        String path = StringUtils.substringAfter(ossVo.getFileName(), Constants.RESOURCE_PREFIX);
+        return CsvUtils.fileCsvToJsonRegex(path);
+    }
 
     @Override
     public String executeQa(QaBo qaBo) {

+ 53 - 40
als-modules/agile-assurance/src/main/java/org/eco/als/service/impl/FormulaService.java

@@ -1,58 +1,72 @@
 package org.eco.als.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
 import com.ql.util.express.DefaultContext;
 import com.ql.util.express.ExpressRunner;
+import com.ql.util.express.InstructionSet;
+import com.ql.util.express.exception.QLBizException;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.eco.als.domain.bo.ExpressBo;
+import org.eco.als.service.IAlgorithmService;
 import org.eco.als.service.IFormulaService;
+import org.eco.als.utils.CheckVibrationDuration;
 import org.eco.common.core.exception.BusinessException;
 import org.springframework.stereotype.Service;
 
 import java.util.HashMap;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
+/**
+ * @author wanggaokun
+ */
 @Service
+@Slf4j
 public class FormulaService implements IFormulaService {
-    private final ExpressRunner runner = new ExpressRunner();
+    @Resource
+    private IAlgorithmService algorithmService;
+    private ExpressRunner runner;
 
     @Override
-    public Object evaluateExpression(String expression, Map<String, Object> variablesMap) throws Exception {
-        DefaultContext<String, Object> context = new DefaultContext<>();
-//        Map<String, Object> variables = CollUtil.isEmpty(variablesMap) ? buildVariableContext(expression) : variablesMap;
-//        context.putAll(variables);
-        // 设置用户参数
-        context.put("age", 21);
-        context.put("monthlyIncome", 8000);
-        context.put("collateralValue", 600000);
-        context.put("creditScore", 680);
-        context.put("hasGuarantor", true);
-
-        expression =
-            "age >= 25 " +
-                "&& (monthlyIncome >= 10000 || collateralValue >= 500000) " +
-                "&& (creditScore >= 700 || hasGuarantor == true)";
-        return runner.execute(expression, context, null, true, false);
+    public Object evaluateExpression(ExpressBo expressBo) throws Exception {
+        runner = new ExpressRunner();
+        runner.addOperatorWithAlias("≠", "!=", null);
+        runner.addOperatorWithAlias("≥", ">=", null);
+        runner.addOperatorWithAlias("≤", "<=", null);
+        // // 注册自定义(持续时间计算)函数到QLExpress上下文
+        runner.addFunction("checkVibrationDuration", new CheckVibrationDuration());
+        runner.setShortCircuit(true);
+        Object result = null;
+        try {
+            runner.parseInstructionSet(expressBo.getExpression());
+            JSONArray arr = algorithmService.getArrayByOssId(expressBo.getOssId());
+            for (Object object : arr) {
+                DefaultContext<String, Object> context = new DefaultContext<>();
+                Map<String, Object> variables = CollUtil.isEmpty(expressBo.getVariables()) ? buildVariableContext(expressBo, (JSONObject) object) : expressBo.getVariables();
+                context.putAll(variables);
+                Boolean isMatch = (Boolean) runner.execute(expressBo.getExpression(), context, null, true, false);
+                log.info("isMatch==========================:{}", isMatch);
+                if (isMatch) {
+                    result = object;
+                    break;
+                }
+            }
+        } catch (QLBizException e) {
+            throw new BusinessException(e.getMessage());
+        }
+        return result;
     }
 
-    private Map<String, Object> buildVariableContext(String expression) {
-        String[] variableNames = parseVariableNames(expression);
+    private Map<String, Object> buildVariableContext(ExpressBo expressBo, JSONObject jsonObject) {
+        String[] variableNames = parseVariableNames(expressBo.getExpression());
         Map<String, Object> variables = new HashMap<>();
         // 字段属性自定义值
         for (String fieldName : variableNames) {
-            switch (fieldName) {
-                case "ratedLife":
-                    variables.put(fieldName, 14);
-                    break;
-                case "usedLife":
-                    variables.put(fieldName, 8);
-                    break;
-                case "uu":
-                    variables.put(fieldName, 2);
-                    break;
-                default:
-                    throw new BusinessException("无此属性公式的配置,请检查: {0}", fieldName);
-            }
+            variables.put(fieldName, jsonObject.get(fieldName));
         }
+        variables.put("时间", jsonObject.get("时间"));
         return variables;
     }
 
@@ -60,12 +74,11 @@ public class FormulaService implements IFormulaService {
      * 解析表达式中的变量名。
      */
     private String[] parseVariableNames(String expression) {
-        Pattern pattern = Pattern.compile("[a-zA-Z][a-zA-Z0-9_]*");
-        Matcher matcher = pattern.matcher(expression);
-        Map<String, Boolean> variables = new HashMap<>();
-        while (matcher.find()) {
-            variables.put(matcher.group(), true);
+        try {
+            InstructionSet instructions = runner.parseInstructionSet(expression);
+            return instructions.getOutAttrNames();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
         }
-        return variables.keySet().toArray(new String[0]);
     }
 }

+ 63 - 0
als-modules/agile-assurance/src/main/java/org/eco/als/utils/CheckVibrationDuration.java

@@ -0,0 +1,63 @@
+package org.eco.als.utils;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.format.FastDateFormat;
+import com.ql.util.express.Operator;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author wanggaokun
+ */
+public class CheckVibrationDuration extends Operator {
+    // 线程安全的状态存储:key=振动值,value=首次达到该值的时间戳(毫秒)
+    private static final Map<String, Long> VIBRATION_STATE = new HashMap<>();
+
+    @Override
+    public Object executeInner(Object[] list) throws Exception {
+        // 参数解析:当前振动值、目标振动值、持续秒数
+        int currentVibration = Integer.parseInt((String) list[0]);
+        String symbol = list[1].toString();
+        int targetVibration = (int) list[2];
+        int durationSeconds = (int) list[3];
+        FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS");
+        long firstTimeDefault = DateUtil.parse((String) list[4], format).getTime();
+        boolean result = false;
+        switch (symbol) {
+            case "=":
+                result = currentVibration == targetVibration;
+                break;
+            case "<":
+                result = currentVibration < targetVibration;
+                break;
+            case ">":
+                result = currentVibration > targetVibration;
+                break;
+            case "<=":
+                result = currentVibration <= targetVibration;
+                break;
+            case ">=":
+                result = currentVibration >= targetVibration;
+                break;
+            case "!=":
+                result = currentVibration != targetVibration;
+                break;
+            default:
+        }
+        String key = currentVibration + symbol + targetVibration + durationSeconds;
+        // 如果当前振动值不等于目标值,清除状态并返回false
+        if (!result) {
+            VIBRATION_STATE.remove(key);
+            return false;
+        }
+        // 获取或记录首次达到目标值的时间戳
+        long firstTime = VIBRATION_STATE.getOrDefault(key, firstTimeDefault);
+        VIBRATION_STATE.putIfAbsent(key, firstTime);
+
+        // 计算已持续时间(秒)
+        long elapsedSeconds = (firstTimeDefault - firstTime) / 1000;
+        // 返回是否满足持续时间要求
+        return elapsedSeconds >= durationSeconds;
+    }
+}

+ 24 - 0
als-modules/agile-assurance/src/main/java/org/eco/als/utils/CsvUtils.java

@@ -133,6 +133,30 @@ public class CsvUtils {
         }
         return jsonArray;
     }
+    /**
+     * csv文件转Json
+     *
+     * @param path path
+     */
+    public static JSONArray fileCsvToJsonRegex(String path) {
+        String csvFilePath = EcoConfig.getProfile() + path;
+        List<CsvRow> rows = getCsvRowList(csvFilePath);
+        // 获取CSV表头,即第一行数据
+        assert rows != null;
+        List<String> headers = rows.get(0).getRawList();
+        // 去除第一行,保留后续数据
+        List<CsvRow> dataLines = rows.subList(1, rows.size());
+        JSONArray jsonArray = new JSONArray();
+        for (CsvRow line : dataLines) {
+            JSONObject jsonObject = new JSONObject();
+            for (int i = 0; i < headers.size(); i++) {
+                String result = headers.get(i).replaceAll("[/+\\-*=<>]", "");
+                jsonObject.putOnce(result, line.getRawList().get(i));
+            }
+            jsonArray.add(jsonObject);
+        }
+        return jsonArray;
+    }
 
     /**
      * 处理异常值