Przeglądaj źródła

feat(actionConfig):动作配置表相关功能实现

HMY 11 miesięcy temu
rodzic
commit
1db5a2c588

+ 3 - 0
admin/src/main/java/com/dcs/hnyz/service/IActionBindingService.java

@@ -65,4 +65,7 @@ public interface IActionBindingService
      * @return
      */
     int changeStatus(ActionBinding actionBinding);
+
+    //通过动作id(actionId)查询所有同步触发的动作id(bindActionId)
+    List<Long> selectBindActionIdsByActionId(Long actionId);
 }

+ 5 - 1
admin/src/main/java/com/dcs/hnyz/service/IEquipmentParamService.java

@@ -73,7 +73,11 @@ public interface IEquipmentParamService
     Map<Integer, Integer> getEquipmentIndexList();
 
     /**
-     * 根据父设备id获取对应寄存器唯一code,用于告警配置
+     * 根据父设备id获取对应主数据寄存器唯一code
      */
     String getCodeByParentId(Long parentId);
+    /**
+     * 根据父设备id获取对应主设置项寄存器地址
+     */
+    Integer getSetCodeByParentId(Long parentId);
 }

+ 14 - 0
admin/src/main/java/com/dcs/hnyz/service/impl/ActionBindingServiceImpl.java

@@ -2,6 +2,10 @@ package com.dcs.hnyz.service.impl;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dcs.common.enums.GeneralStatus;
 import com.dcs.common.utils.DateUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -101,4 +105,14 @@ public class ActionBindingServiceImpl implements IActionBindingService
     public int changeStatus(ActionBinding actionBinding) {
         return actionBindingMapper.updateById(actionBinding);
     }
+
+    @Override
+    public List<Long> selectBindActionIdsByActionId(Long actionId) {
+        LambdaQueryWrapper<ActionBinding> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(ActionBinding::getActionId,actionId);
+        wrapper.eq(ActionBinding::getStatus, GeneralStatus.ENABLE.getCode());
+        wrapper.select(ActionBinding::getBindActionId);
+        List<ActionBinding> actionBindings = actionBindingMapper.selectList(wrapper);
+        return actionBindings.stream().map(ActionBinding::getBindActionId).collect(Collectors.toList());
+    }
 }

+ 234 - 22
admin/src/main/java/com/dcs/hnyz/service/impl/ActionConfigServiceImpl.java

@@ -1,97 +1,309 @@
 package com.dcs.hnyz.service.impl;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
 import com.dcs.common.utils.DateUtils;
+import com.dcs.equipment.task.ModbusTcpTask;
+import com.dcs.equipment.utils.ModbusUtil;
+import com.dcs.hnyz.domain.Equipment;
+import com.dcs.hnyz.domain.bo.CachedAction;
+import com.dcs.hnyz.domain.condition.Condition;
+import com.dcs.hnyz.domain.condition.ConditionGroup;
+import com.dcs.hnyz.domain.condition.ConditionItem;
+import com.dcs.hnyz.domain.vo.EquipmentParamFormVo;
+import com.dcs.hnyz.enums.ActionTypeEnum;
+import com.dcs.hnyz.enums.DeviceTypeEnum;
+import com.dcs.hnyz.enums.OperationType;
+import com.dcs.hnyz.enums.TriggerTypeEnum;
+import com.dcs.hnyz.service.IActionBindingService;
+import com.dcs.hnyz.service.IEquipmentParamService;
+import com.dcs.hnyz.service.IEquipmentService;
+import com.dcs.hnyz.utils.ActionCacheBuilder;
+import com.dcs.hnyz.utils.RegisterCodeMapBuilder;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
+import com.intelligt.modbus.jlibmodbus.exception.ModbusNumberException;
+import com.intelligt.modbus.jlibmodbus.exception.ModbusProtocolException;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import com.dcs.hnyz.mapper.ActionConfigMapper;
 import com.dcs.hnyz.domain.ActionConfig;
 import com.dcs.hnyz.service.IActionConfigService;
 
+import javax.annotation.PreDestroy;
+import javax.annotation.Resource;
+
+import static com.dcs.framework.datasource.DynamicDataSourceContextHolder.log;
+
 /**
  * 动作配置管理Service业务层处理
- * 
+ *
  * @author hmy
  * @date 2025-04-28
  */
 @Service
-public class ActionConfigServiceImpl implements IActionConfigService 
-{
-    @Autowired
+public class ActionConfigServiceImpl implements IActionConfigService {
+    @Resource
     private ActionConfigMapper actionConfigMapper;
+    @Autowired
+    private ActionCacheBuilder actionCacheBuilder;
+    @Autowired
+    private RegisterCodeMapBuilder registerCodeMapBuilder;
+    @Autowired
+    private IEquipmentParamService equipmentParamService;
+    @Autowired
+    private IActionBindingService actionBindingService;
+
+    //共享线程池
+    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);
+
 
     /**
      * 查询动作配置管理
-     * 
+     *
      * @param actionId 动作配置管理主键
      * @return 动作配置管理
      */
     @Override
-    public ActionConfig selectActionConfigByActionId(Long actionId)
-    {
+    public ActionConfig selectActionConfigByActionId(Long actionId) {
         return actionConfigMapper.selectById(actionId);
     }
 
     /**
      * 查询动作配置管理列表
-     * 
+     *
      * @param actionConfig 动作配置管理
      * @return 动作配置管理
      */
     @Override
-    public List<ActionConfig> selectActionConfigList(ActionConfig actionConfig)
-    {
+    public List<ActionConfig> selectActionConfigList(ActionConfig actionConfig) {
         return actionConfigMapper.selectActionConfigList(actionConfig);
     }
 
     /**
      * 新增动作配置管理
-     * 
+     *
      * @param actionConfig 动作配置管理
      * @return 结果
      */
     @Override
-    public int insertActionConfig(ActionConfig actionConfig)
-    {
+    public int insertActionConfig(ActionConfig actionConfig) {
         actionConfig.setCreateTime(DateUtils.getNowDate());
         return actionConfigMapper.insert(actionConfig);
     }
 
     /**
      * 修改动作配置管理
-     * 
+     *
      * @param actionConfig 动作配置管理
      * @return 结果
      */
     @Override
-    public int updateActionConfig(ActionConfig actionConfig)
-    {
+    public int updateActionConfig(ActionConfig actionConfig) {
         actionConfig.setUpdateTime(DateUtils.getNowDate());
         return actionConfigMapper.updateById(actionConfig);
     }
 
     /**
      * 批量删除动作配置管理
-     * 
+     *
      * @param actionIds 需要删除的动作配置管理主键
      * @return 结果
      */
     @Override
-    public int deleteActionConfigByActionIds(Long[] actionIds)
-    {
+    public int deleteActionConfigByActionIds(Long[] actionIds) {
         return actionConfigMapper.deleteBatchIds(Arrays.asList(actionIds));
     }
 
     /**
      * 删除动作配置管理信息
-     * 
+     *
      * @param actionId 动作配置管理主键
      * @return 结果
      */
     @Override
-    public int deleteActionConfigByActionId(Long actionId)
-    {
+    public int deleteActionConfigByActionId(Long actionId) {
         return actionConfigMapper.deleteById(actionId);
     }
+
+    /**
+     * 递归判断条件是否满足(核心逻辑)
+     * @param condition 条件
+     * @param deviceMap 设备ID到寄存器code的映射
+     * @param plcData   实时数据
+     * @return 是否满足条件
+     */
+    public boolean evaluate(Condition condition,
+                            Map<Integer, String> deviceMap,
+                            Map<String, EquipmentParamFormVo> plcData) {
+        if (condition instanceof ConditionGroup) {//判断是否为组合条件
+            ConditionGroup group = (ConditionGroup) condition;
+            boolean result = "AND".equalsIgnoreCase(group.getOperator());
+            for (Condition child : group.getConditions()) {//递归判断子条件
+                boolean childResult = evaluate(child, deviceMap, plcData);
+                if ("AND".equalsIgnoreCase(group.getOperator())) {
+                    result = result && childResult;
+                    if (!result) break; // 短路优化
+                } else {
+                    result = result || childResult;
+                    if (result) break;
+                }
+            }
+            return result;
+        } else if (condition instanceof ConditionItem) {// 判断是否为叶子条件
+            ConditionItem item = (ConditionItem) condition;
+            int deviceId = item.getDevice();
+            String operator = item.getOperator();
+            String expectedValue = item.getValue();
+
+            String code = deviceMap.get(deviceId);
+            if (code == null) return false;
+
+            EquipmentParamFormVo param = plcData.get(code);
+            if (param == null || param.getValue() == null) return false;
+
+            String actualValue = param.getValue().toString();//实际值
+            //判断条件是否满足
+            try {
+                OperationType op = OperationType.fromCode(operator);
+                return op.apply(actualValue, expectedValue);
+            } catch (Exception e) {
+                e.printStackTrace();
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 把将 paramList 处理为 Map<String, EquipmentParamFormVo>
+     */
+    public Map<String, EquipmentParamFormVo> paramListToMap(List<EquipmentParamFormVo> paramList) {
+        return paramList.stream()
+                .filter(p -> p.getCode() != null && p.getValue() != null)
+                .collect(Collectors.toMap(
+                        EquipmentParamFormVo::getCode,
+                        p -> p,
+                        (existing, replacement) -> replacement // 如果有重复 Code,用后者覆盖
+                ));
+    }
+
+    @Scheduled(fixedRate = 3000) // 每 3 秒执行一次
+    public void checkActions() {
+        EquipmentParamFormVo param = new EquipmentParamFormVo();
+//        param.setCode("D2kxh");
+//        param.setValue(true);
+        Map<String, EquipmentParamFormVo> plcData = paramListToMap(Collections.singletonList(param));
+        for (CachedAction action : actionCacheBuilder.getCache().values()) {
+            ActionConfig actionConfig = action.getActionConfig();//动作配置
+            //只处理条件触发事件
+            if (!TriggerTypeEnum.CONDITION_TRIGGER.getCode().equals(actionConfig.getTriggerType())) {
+                continue;
+            }
+            //对于已触发的事件不在重复触发
+            if (action.isTrigger()) continue;
+//            boolean match = evaluate(action.getCondition(), registerCodeMapBuilder.getRegisterCodeMap(),paramListToMap(ModbusTcpTask.getBroadCastEquipmentParamFormVOList()));
+            boolean match = evaluate(action.getCondition(), registerCodeMapBuilder.getRegisterCodeMap(), plcData);
+            //修改缓存内动作触发状态
+            actionCacheBuilder.setTrigger(actionConfig.getActionId(), match);
+            if (match) {
+                log.info("动作命中:ID={}, 缓存={}", actionConfig.getActionId(), action.getCondition());
+                triggerActionDelay(actionConfig);
+                //处理同步触发事件
+                List<Long> longs = actionBindingService.selectBindActionIdsByActionId(actionConfig.getActionId());
+                for (Long actionId : longs) {
+                    //对应同步事件触发要满足的动作配置
+                    CachedAction actionSync = actionCacheBuilder.getCache().get(actionId);
+                    //对于已触发的事件不在重复触发
+                    if (actionSync.isTrigger()) continue;
+                    ActionConfig actionConfigSync=actionSync.getActionConfig();
+                    Condition condition = actionSync.getCondition();//动作条件
+                    boolean matchSync = evaluate(condition, registerCodeMapBuilder.getRegisterCodeMap(), plcData);
+                    if (matchSync) {
+                        log.info("动作命中:ID={}, 缓存={}", actionConfigSync.getActionId(), actionSync.getCondition());
+                        triggerActionDelay(actionConfigSync);
+                    }
+                    actionCacheBuilder.setTrigger(actionConfigSync.getActionId(), matchSync);
+                }
+            }
+        }
+    }
+
+    /**
+     * 延时处理
+     * @param actionConfig 动作配置
+     */
+    private void triggerActionDelay(ActionConfig actionConfig) {
+        //延时处理
+        if (actionConfig.getTriggerDelay() > 0) {
+            scheduler.schedule(() -> {
+                log.info("延时动作执行:ID={}", actionConfig.getActionId());
+                executeAction(actionConfig);
+            }, actionConfig.getTriggerDelay(), TimeUnit.SECONDS);
+        } else {
+            log.info("动作执行:ID={}", actionConfig.getActionId());
+            executeAction(actionConfig);
+        }
+    }
+
+
+    /**
+     * 执行动作逻辑
+     * @param actionConfig 动作配置
+     */
+    public void executeAction(ActionConfig actionConfig) {
+        //动作执行对应寄存器的code
+        Integer address = equipmentParamService.getSetCodeByParentId(actionConfig.getEquipmentId());
+        if (address == null) {
+            log.info("动作执行对应寄存器的code为空");
+            return;
+        }
+        //动作类型
+        ActionTypeEnum type;
+        try {
+            type = ActionTypeEnum.valueOf(actionConfig.getActionType());
+        } catch (IllegalArgumentException e) {
+            log.warn("未知动作类型:{}", actionConfig.getActionType());
+            return;
+        }
+        try {
+            switch (type) {
+                case SET_FLOAT_VALUE:
+                    ModbusUtil.setRegisterFloatValue(1, address, Float.parseFloat(actionConfig.getValue()));
+                    break;
+                case ON:
+//                    System.out.println("on" + address);
+                    ModbusUtil.setCoilValue(1, address, true);
+                    break;
+                case OFF:
+//                    System.out.println("off" + address);
+                    ModbusUtil.setCoilValue(1, address, false);
+                    break;
+                case SET_INT_VALUE:
+                    ModbusUtil.setRegisterValue(1, address, Integer.parseInt(actionConfig.getValue()));
+                    break;
+                default:
+                    log.warn("未处理的动作类型:{}", type);
+                    break;
+            }
+        } catch (Exception e) {
+            log.error("执行动作失败,类型={},寄存器地址={},值={},异常信息:{}",
+                    type, address, actionConfig.getValue(), e.getMessage(), e);
+        }
+    }
+
+    @PreDestroy
+    public void shutdownScheduler() {
+        scheduler.shutdown();
+    }
+
 }

+ 2 - 24
admin/src/main/java/com/dcs/hnyz/service/impl/AlarmConfigServiceImpl.java

@@ -151,22 +151,6 @@ public class AlarmConfigServiceImpl implements IAlarmConfigService {
         return codeConfigMap.getOrDefault(code, Collections.emptyList());
     }
 
-
-    /**
-     * 判断单条告警是否超过阈值,value 是设备实时数据
-     */
-    private boolean checkAlarmTrigger(double value, AlarmConfig config) {
-        if (config.getThresholdValue() == null) return false;
-        OperationType op;
-        try {
-            op = config.getThresholdType();
-        } catch (IllegalArgumentException e) {
-            return false;
-        }
-        return op.apply(value, config.getThresholdValue());
-    }
-
-
     /**
      * 判断单条告警是否触发
      *
@@ -176,7 +160,7 @@ public class AlarmConfigServiceImpl implements IAlarmConfigService {
      * @param param  寄存器相关参数
      * @return
      */
-    private AlarmEvent checkAlarmWithDelay(String code, AlarmConfig config, double value, EquipmentParamFormVo param) {
+    private AlarmEvent checkAlarmWithDelay(String code, AlarmConfig config, String value, EquipmentParamFormVo param) {
         String key = code + "_" + config.getAlarmCode();
         long now = System.currentTimeMillis();
         Date nowDate = new Date(now);
@@ -264,16 +248,10 @@ public class AlarmConfigServiceImpl implements IAlarmConfigService {
             if (param == null) continue;
 
             String valueStr = param.getValue().toString();
-            double value;
-            try {
-                value = Double.parseDouble(valueStr);
-            } catch (NumberFormatException e) {
-                continue;
-            }
             //获取当前告警配置 Map<String, List<AlarmConfig>> 中的一个 code 所对应的告警配置列表。 并将这个 List<AlarmConfig> 转为 Stream
             entry.getValue().stream()
                     //过滤出真正触发的告警事件、告警恢复事件,转换成一个 AlarmEvent 对象,前端弹窗提示
-                    .map(config -> checkAlarmWithDelay(code, config, value, param))
+                    .map(config -> checkAlarmWithDelay(code, config, valueStr, param))
                     .filter(Objects::nonNull)
                     //把每个构建好的 AlarmEvent 添加到结果列表 triggeredAlarms
                     .forEach(triggeredAlarms::add);

+ 59 - 9
admin/src/main/java/com/dcs/hnyz/service/impl/EquipmentParamServiceImpl.java

@@ -1,6 +1,7 @@
 package com.dcs.hnyz.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dcs.common.enums.GeneralStatus;
 import com.dcs.common.exception.CustomException;
 import com.dcs.equipment.task.ModbusTcpTask;
 import com.dcs.hnyz.domain.Equipment;
@@ -13,8 +14,11 @@ import com.dcs.hnyz.mapper.EquipmentParamMapper;
 import com.dcs.hnyz.service.IEquipmentParamService;
 import com.dcs.hnyz.service.IEquipmentService;
 import eu.bitwalker.useragentutils.DeviceType;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.event.EventListener;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -37,6 +41,9 @@ public class EquipmentParamServiceImpl implements IEquipmentParamService {
     @Resource
     private IEquipmentService equipmentService;
 
+    //设备id-->寄存器唯一code,用于配置
+//    private Map<Integer,String> registerCodeMap = new HashMap<>();
+
     /**
      * 查询设备参数管理
      *
@@ -132,7 +139,7 @@ public class EquipmentParamServiceImpl implements IEquipmentParamService {
         return swappedMap;
     }
 
-    // TODO 获取不同设备类型的告警配置对应的寄存器唯一code(暂时只处理了传感器相关)
+    // TODO 根据设备id获取对应的主数据所在寄存器的唯一code(暂时只处理了传感器和阀门相关)
     @Override
     public String getCodeByParentId(Long parentId) {
         if(parentId == null) {
@@ -140,29 +147,72 @@ public class EquipmentParamServiceImpl implements IEquipmentParamService {
             return null;
         }
         Equipment e = equipmentService.selectEquipmentByEquipmentId(parentId);
-        DeviceTypeEnum type = DeviceTypeEnum.fromCode(e.getEquipmentType());
-        if (type == null) {
-            log.warn("未知设备类型: {}", e.getEquipmentType());
+        if (e == null) {
+            log.warn("根据 parentId:{} 没有查询到设备信息", parentId);
             return null;
         }
+        DeviceTypeEnum type = DeviceTypeEnum.fromCode(e.getEquipmentType());
         LambdaQueryWrapper<EquipmentParam> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(EquipmentParam::getRelationId, parentId);
         switch (type) {
             case SENSOR:
+            case VALVE_WITH_FEEDBACK:
+                // 处理有反馈阀门逻辑
                 // 处理传感器逻辑
-                wrapper.eq(EquipmentParam::getParamType, RegisterTypeEnum.READ.getCode());
+                wrapper.eq(EquipmentParam::getParamType, RegisterTypeEnum.MASTER_DATA.getCode());
+                break;
+            case VALVE_NO_FEEDBACK:
+                // 处理无反馈阀门逻辑(用控制信号来代替反馈信号)
+                wrapper.eq(EquipmentParam::getParamType, RegisterTypeEnum.MASTER_SET.getCode());
                 break;
             case PUMP:
-                // 处理水泵逻辑
-                return null;
-            case VALVE:
-                // 处理阀门逻辑
+                // 处理泵逻辑
                 return null;
             default:
                 // 其他类型
                 return null;
         }
         EquipmentParam ep = equipmentParamMapper.selectOne(wrapper);
+        if (ep == null) {
+            log.warn("根据 parentId:{} 查询不到设备参数1", parentId);
+            return null;
+        }
         return ep.getCode();
     }
+
+    @Override
+    public Integer getSetCodeByParentId(Long parentId) {
+        if(parentId == null) {
+            log.warn("parentId is null");
+            return null;
+        }
+        Equipment e = equipmentService.selectEquipmentByEquipmentId(parentId);
+        if (e == null) {
+            log.warn("根据 parentId:{} 没有查询到设备信息", parentId);
+            return null;
+        }
+        DeviceTypeEnum type = DeviceTypeEnum.fromCode(e.getEquipmentType());
+        LambdaQueryWrapper<EquipmentParam> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(EquipmentParam::getRelationId, parentId);
+        switch (type) {
+            case SENSOR:
+            case VALVE_WITH_FEEDBACK:
+            case VALVE_NO_FEEDBACK:
+                // 处理传感器,阀门逻辑
+                wrapper.eq(EquipmentParam::getParamType, RegisterTypeEnum.MASTER_SET.getCode());
+                break;
+            case PUMP:
+                // 处理泵逻辑
+                return null;
+            default:
+                // 其他类型
+                return null;
+        }
+        EquipmentParam ep = equipmentParamMapper.selectOne(wrapper);
+        if (ep == null) {
+            log.warn("根据 parentId:{} 查询不到设备参数2", parentId);
+            return null;
+        }
+        return ep.getAddress();
+    }
 }

+ 55 - 0
admin/src/main/java/com/dcs/hnyz/utils/ConditionParser.java

@@ -0,0 +1,55 @@
+package com.dcs.hnyz.utils;
+
+import com.dcs.hnyz.domain.condition.Condition;
+import com.dcs.hnyz.domain.condition.ConditionGroup;
+import com.dcs.hnyz.domain.condition.ConditionItem;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ConditionParser
+ * 条件解析器
+ * @author: hmy
+ * @date: 2025/5/27 9:55
+ */
+public class ConditionParser {
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    public static Condition parse(JsonNode node) {
+        if (node == null || node.isNull() || !node.has("type")) {
+            throw new IllegalArgumentException("无效的条件节点:缺少 type 字段或节点为 null");
+        }
+        String type = node.get("type").asText();
+        if ("group".equals(type)) {
+            ConditionGroup group = new ConditionGroup();
+            group.setOperator(node.get("operator").asText());
+            ArrayNode children = (ArrayNode) node.get("conditions");
+            List<Condition> conditionList = new ArrayList<>();
+            for (JsonNode child : children) {
+                conditionList.add(parse(child));
+            }
+            group.setConditions(conditionList);
+            return group;
+        } else if ("item".equals(type)) {
+            ConditionItem item = new ConditionItem();
+            item.setDevice(node.get("equipmentId").asInt());
+            item.setOperator(node.get("operator").asText());
+            item.setValue(node.get("value").asText());
+            return item;
+        }
+        throw new IllegalArgumentException("Unknown condition type: " + type);
+    }
+
+    /**
+     * 从 JSON 字符串解析为 Condition 对象
+     */
+    public static Condition fromJsonString(String jsonStr) throws Exception {
+        JsonNode root = mapper.readTree(jsonStr);
+        return parse(root);
+    }
+}