Pārlūkot izejas kodu

feature: 智能控制V2版本 第五版(日志)

Jayhaw 3 nedēļas atpakaļ
vecāks
revīzija
74d2b5a426
23 mainītis faili ar 389 papildinājumiem un 59 dzēšanām
  1. 12 0
      src/main/java/cn/sciento/farm/automationv2/api/dto/GatewayControlDto.java
  2. 10 10
      src/main/java/cn/sciento/farm/automationv2/app/handler/DeviceControlHelper.java
  3. 1 1
      src/main/java/cn/sciento/farm/automationv2/app/handler/impl/SetPumpPressureNodeHandler.java
  4. 2 2
      src/main/java/cn/sciento/farm/automationv2/app/handler/impl/WaitNodeHandler.java
  5. 44 0
      src/main/java/cn/sciento/farm/automationv2/app/handler/impl/ZoneSwitchWaitNodeHandler.java
  6. 1 1
      src/main/java/cn/sciento/farm/automationv2/app/service/RetryManager.java
  7. 28 22
      src/main/java/cn/sciento/farm/automationv2/app/service/TaskExecutionEngine.java
  8. 7 0
      src/main/java/cn/sciento/farm/automationv2/domain/business/DeviceBusiness.java
  9. 9 2
      src/main/java/cn/sciento/farm/automationv2/domain/business/impl/DeviceBusinessImpl.java
  10. 36 0
      src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskGroupNodeVO.java
  11. 35 0
      src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskGroupVO.java
  12. 50 0
      src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskLog.java
  13. 50 0
      src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskMainVO.java
  14. 4 0
      src/main/java/cn/sciento/farm/automationv2/domain/service/ExecutionPlanGenerator.java
  15. 6 0
      src/main/java/cn/sciento/farm/automationv2/domain/valueobject/DeviceInfo.java
  16. 20 2
      src/main/java/cn/sciento/farm/automationv2/domain/valueobject/ExecutionNode.java
  17. 10 1
      src/main/java/cn/sciento/farm/automationv2/domain/valueobject/ExecutionPlan.java
  18. 9 1
      src/main/java/cn/sciento/farm/automationv2/infra/feign/DeviceFeign.java
  19. 5 0
      src/main/java/cn/sciento/farm/automationv2/infra/feign/fallback/DeviceFallback.java
  20. 5 5
      src/main/java/cn/sciento/farm/automationv2/infra/mq/consumer/CheckAckConsumer.java
  21. 13 10
      src/main/java/cn/sciento/farm/automationv2/infra/mq/producer/FlowControlProducer.java
  22. 30 0
      src/main/resources/application.yml
  23. 2 2
      src/main/resources/bootstrap.yml

+ 12 - 0
src/main/java/cn/sciento/farm/automationv2/api/dto/GatewayControlDto.java

@@ -27,6 +27,12 @@ public class GatewayControlDto {
     @NotBlank(message = "设备控制回路不能为空")
     private String circuit;
 
+    /**
+     * 控制节点
+     */
+    @NotNull(message = "控制节点不能为空")
+    private Long switchId;
+
     @NotNull(message = "设备控制标志不能为空!")
     private Long sw;
 
@@ -49,4 +55,10 @@ public class GatewayControlDto {
     @ApiModelProperty("4G球阀 - 设置容量值,大于0且`type`非0时不生效。")
     private Float capacity;
 
+    @ApiModelProperty("公共ID")
+    private Long concentratorId;
+
+    @ApiModelProperty("给定压力(单位:KPa,分辨率:1)")
+    private Float givenPressure;
+
 }

+ 10 - 10
src/main/java/cn/sciento/farm/automationv2/app/handler/DeviceControlHelper.java

@@ -46,9 +46,10 @@ public class DeviceControlHelper {
      */
     public static GatewayControlDto buildBallValveAngleDto(DeviceInfo device, Integer targetAngle, Long tenantId) {
         GatewayControlDto dto = new GatewayControlDto();
-        dto.setEui(device.getEui());
-        dto.setNode(device.getNode());
-        dto.setCircuit(device.getCircuit());
+//        dto.setEui(device.getEui());
+//        dto.setNode(device.getNode());
+//        dto.setCircuit(device.getCircuit());
+        dto.setSwitchId(device.getSwitchId());
         dto.setSw(1L); // 球阀控制开启
         dto.setTenantId(tenantId);
         dto.setBallValuePercent(targetAngle); // 设置角度百分比
@@ -62,9 +63,10 @@ public class DeviceControlHelper {
      */
     public static GatewayControlDto buildStartPumpDto(DeviceInfo device, Long tenantId) {
         GatewayControlDto dto = new GatewayControlDto();
-        dto.setEui(device.getEui());
-        dto.setNode(device.getNode());
-        dto.setCircuit(device.getCircuit());
+//        dto.setEui(device.getEui());
+//        dto.setNode(device.getNode());
+//        dto.setCircuit(device.getCircuit());
+        dto.setSwitchId(device.getSwitchId());
         dto.setSw(1L); // 启动
         dto.setTenantId(tenantId);
         dto.setRemark("本次控制由智能控制V2调用 - 启动水泵");
@@ -92,10 +94,8 @@ public class DeviceControlHelper {
      */
     public static GatewayControlDto buildSetPumpPressureDto(DeviceInfo device, Integer pressureKpa, Long tenantId) {
         GatewayControlDto dto = new GatewayControlDto();
-        dto.setEui(device.getEui());
-        dto.setNode(device.getNode());
-        dto.setCircuit(device.getCircuit());
-        dto.setSw(2L); // 设置压力使用特殊标志
+        dto.setConcentratorId(device.getDeviceId());
+        dto.setGivenPressure(Float.valueOf(pressureKpa)); // 设置压力使用特殊标志
         dto.setTenantId(tenantId);
         dto.setRemark("本次控制由智能控制V2调用 - 设置水泵压力: " + pressureKpa + "kPa");
         dto.setStaffName("第三方远程调用");

+ 1 - 1
src/main/java/cn/sciento/farm/automationv2/app/handler/impl/SetPumpPressureNodeHandler.java

@@ -66,7 +66,7 @@ public class SetPumpPressureNodeHandler implements NodeHandler {
         try {
             // 下发设置压力指令
             GatewayControlDto dto = DeviceControlHelper.buildSetPumpPressureDto(pumpDevice, targetPressure, tenantId);
-            Integer result = deviceBusiness.control(dto);
+            Integer result = deviceBusiness.pumpPressure(dto);
 
             log.info("设置水泵压力,executionId={}, pumpId={}, pressureKpa={}, result={}",
                     executionId, pumpId, targetPressure, result);

+ 2 - 2
src/main/java/cn/sciento/farm/automationv2/app/handler/impl/WaitNodeHandler.java

@@ -29,8 +29,8 @@ public class WaitNodeHandler implements NodeHandler {
 
         int waitSeconds = node.getWaitSeconds();
 
-        log.info("执行等待节点,executionId={}, nodeIndex={}, waitSeconds={}({}分钟)",
-                executionId, nodeIndex, waitSeconds, waitSeconds / 60);
+        log.info("执行等待节点,executionId={}, nodeIndex={}, waitSeconds={}({})",
+                executionId, nodeIndex, waitSeconds, waitSeconds );
 
         // WAIT节点不需要下发设备指令
         // 只需要在执行引擎中发送延迟MQ消息(等待时长后推进到下一节点)

+ 44 - 0
src/main/java/cn/sciento/farm/automationv2/app/handler/impl/ZoneSwitchWaitNodeHandler.java

@@ -0,0 +1,44 @@
+package cn.sciento.farm.automationv2.app.handler.impl;
+
+import cn.sciento.farm.automationv2.app.context.ExecutionContext;
+import cn.sciento.farm.automationv2.app.handler.NodeHandler;
+import cn.sciento.farm.automationv2.domain.enums.NodeType;
+import cn.sciento.farm.automationv2.domain.valueobject.ExecutionNode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 稳压节点处理器
+ * 功能:稳压等待指定时长(灌区切换时长)
+ * 注意:ZONE_SWITCH_WAIT节点不下发任何设备指令,仅发送延迟MQ消息
+ */
+@Slf4j
+@Component
+public class ZoneSwitchWaitNodeHandler implements NodeHandler {
+
+    @Override
+    public NodeType getType() {
+        return NodeType.ZONE_SWITCH_WAIT;
+    }
+
+    @Override
+    public void execute(ExecutionContext context) {
+        ExecutionNode node = context.getCurrentNode();
+        Long executionId = context.getExecutionId();
+        Integer nodeIndex = context.getCurrentIndex();
+
+        int waitSeconds = node.getWaitSeconds();
+
+        log.info("执行等待节点,executionId={}, nodeIndex={}, waitSeconds={}({}秒)",
+                executionId, nodeIndex, waitSeconds, waitSeconds );
+
+        // WAIT节点不需要下发设备指令
+        // 只需要在执行引擎中发送延迟MQ消息(等待时长后推进到下一节点)
+    }
+
+    @Override
+    public int getWaitSeconds(ExecutionContext context) {
+        ExecutionNode node = context.getCurrentNode();
+        return node.getWaitSeconds();
+    }
+}

+ 1 - 1
src/main/java/cn/sciento/farm/automationv2/app/service/RetryManager.java

@@ -36,7 +36,7 @@ public class RetryManager {
     /**
      * 最大重试次数
      */
-    private static final int MAX_RETRY_COUNT = 2;
+    private static final int MAX_RETRY_COUNT = 3;
 
     /**
      * 重试延迟(秒):第1次0秒,第2次5秒,第3次15秒

+ 28 - 22
src/main/java/cn/sciento/farm/automationv2/app/service/TaskExecutionEngine.java

@@ -147,7 +147,6 @@ public class TaskExecutionEngine {
             } else {
                 // WAIT节点:不需要ACK,直接推进
                 int waitSeconds = handler.getWaitSeconds(context);
-                int waitMinutes = waitSeconds / 60;
 
                 // 更新节点状态为SUCCESS
                 currentNode.setStatus(NodeStatus.SUCCESS.name());
@@ -157,14 +156,14 @@ public class TaskExecutionEngine {
                 // 更新执行计划到Redis
                 executionPlanStore.save(executionId, plan);
 
-                // 标记推进(幂等)
-                idempotencyManager.markProceed(executionId, nodeIndex);
+                // 幂等性保护:标记节点已推进
+//                idempotencyManager.markProceed(executionId, nodeIndex);
 
                 // 发送NEXT_NODE延迟消息
-                flowControlProducer.sendNextNode(executionId, nodeIndex, waitMinutes);
+                flowControlProducer.sendNextNode(executionId, nodeIndex, waitSeconds);
 
-                log.info("WAIT节点执行完成,等待{}分钟后推进,executionId={}, nodeIndex={}",
-                        waitMinutes, executionId, nodeIndex);
+                log.info("WAIT节点执行完成,等待{}后推进,executionId={}, nodeIndex={}",
+                        waitSeconds, executionId, nodeIndex);
             }
 
         } catch (Exception e) {
@@ -226,13 +225,13 @@ public class TaskExecutionEngine {
             log.info("设备ACK全部成功,准备推进,executionId={}, nodeIndex={}",
                     executionId, nodeIndex);
 
-            // 标记推进(幂等)
-            boolean canProceed = idempotencyManager.markProceed(executionId, nodeIndex);
-            if (!canProceed) {
-                log.warn("节点已被推进,忽略重复推进,executionId={}, nodeIndex={}",
-                        executionId, nodeIndex);
-                return;
-            }
+            // 幂等性保护:防止多个CHECK_ACK消息重复发送NEXT_NODE
+//            boolean canProceed = idempotencyManager.markProceed(executionId, nodeIndex);
+//            if (!canProceed) {
+//                log.info("节点已被标记推进(可能是重复的CHECK_ACK消息),忽略,executionId={}, nodeIndex={}",
+//                        executionId, nodeIndex);
+//                return;
+//            }
 
             // 更新节点状态为SUCCESS
             node.setStatus(NodeStatus.SUCCESS.name());
@@ -305,7 +304,7 @@ public class TaskExecutionEngine {
         if (!plan.hasNext(execution.getCurrentIndex())) {
             // 全部节点执行完成
             log.info("所有节点执行完成,任务成功,executionId={}", executionId);
-
+            execution.setExecutionPlan(plan);
             execution.markAsSuccess();
             execution.setFinishedAt(LocalDateTime.now());
             taskExecutionRepository.updateByPrimaryKeySelective(execution);
@@ -355,6 +354,7 @@ public class TaskExecutionEngine {
         // 更新节点状态为FAILED
         ExecutionPlan plan = executionPlanStore.get(executionId);
         if (plan != null) {
+            execution.setExecutionPlan(plan);
             ExecutionNode node = plan.getNode(nodeIndex);
             if (node != null) {
                 node.setStatus(NodeStatus.FAILED.name());
@@ -393,7 +393,7 @@ public class TaskExecutionEngine {
         // 发送报警通知(异步,不阻塞)
         if (alarmId != null) {
             try {
-                alarmNotificationService.sendAlarm(executionId);
+//                alarmNotificationService.sendAlarm(executionId);
             } catch (Exception e) {
                 log.error("发送报警通知异常,executionId={}, alarmId={}", executionId, alarmId, e);
                 // 不抛出异常,避免影响主流程
@@ -413,6 +413,19 @@ public class TaskExecutionEngine {
     public void handleAckTimeout(Long executionId, Integer nodeIndex) {
         log.warn("ACK超时兜底处理,executionId={}, nodeIndex={}", executionId, nodeIndex);
 
+        // 加载执行实例
+        TaskExecution execution = taskExecutionRepository.selectByPrimaryKey(executionId);
+        if (execution == null) {
+            log.error("执行实例不存在,executionId={}", executionId);
+            return;
+        }
+
+        if (execution.isTerminal()) {
+            log.warn("执行已终止,忽略超时处理,executionId={}, status={}",
+                    executionId, execution.getStatus());
+            return;
+        }
+
         // 幂等性检查1:如果节点已推进,忽略超时
         if (idempotencyManager.isProceed(executionId, nodeIndex)) {
             log.info("节点已推进,忽略超时处理,executionId={}, nodeIndex={}",
@@ -423,13 +436,6 @@ public class TaskExecutionEngine {
         // 幂等性检查2:如果超时已处理,忽略重复
         // (已在Consumer中处理)
 
-        // 加载执行实例
-        TaskExecution execution = taskExecutionRepository.selectByPrimaryKey(executionId);
-        if (execution == null) {
-            log.error("执行实例不存在,executionId={}", executionId);
-            return;
-        }
-
         // 检查ACK状态
         ExecutionPlan plan = executionPlanStore.get(executionId);
         if (plan == null) {

+ 7 - 0
src/main/java/cn/sciento/farm/automationv2/domain/business/DeviceBusiness.java

@@ -22,4 +22,11 @@ public interface DeviceBusiness {
      */
     Integer control(GatewayControlDto dto);
 
+
+    /**
+     * 设置水泵压力
+     * @param dto 网关控制dto
+     * @return 是否成功
+     */
+    Integer pumpPressure(GatewayControlDto dto);
 }

+ 9 - 2
src/main/java/cn/sciento/farm/automationv2/domain/business/impl/DeviceBusinessImpl.java

@@ -25,9 +25,16 @@ public class DeviceBusinessImpl implements DeviceBusiness {
 
     @Override
     public Integer control(GatewayControlDto dto) {
-        ResponseEntity<String> result = feign.control(dto.getTenantId(), dto);
-        return ResponseUtils.getResponse(result, Integer.class);
+//        ResponseEntity<String> result = feign.control(dto.getTenantId(), dto);
+//        return ResponseUtils.getResponse(result, Integer.class);
+        return 1;
     }
 
+    @Override
+    public Integer pumpPressure(GatewayControlDto dto) {
+//        ResponseEntity<String> result = feign.PumpPressure(dto.getTenantId(), dto);
+//        return ResponseUtils.getResponse(result, Integer.class);
+        return 1;
+    }
 
 }

+ 36 - 0
src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskGroupNodeVO.java

@@ -0,0 +1,36 @@
+package cn.sciento.farm.automationv2.domain.entity.mongo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 轮灌任务- 灌区 - 各节点VO
+ */
+@Data
+public class IrrigationTaskGroupNodeVO {
+
+    @ApiModelProperty("设备Id")
+    private String deviceId;
+
+    @ApiModelProperty("设备名称")
+    private String deviceName;
+
+    @ApiModelProperty("设备类型:球阀、水泵")
+    private String deviceType;
+
+    @ApiModelProperty("设备动作:开关水泵/开关球阀/设置水泵压力")
+    private String action;
+
+    @ApiModelProperty("操作状态/结果")
+    private String status;
+
+    @ApiModelProperty("设备开始时间")
+    private LocalDateTime deviceBeginTime;
+
+    @ApiModelProperty("设备结束时间")
+    private LocalDateTime deviceEndTime;
+
+    
+}

+ 35 - 0
src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskGroupVO.java

@@ -0,0 +1,35 @@
+package cn.sciento.farm.automationv2.domain.entity.mongo;
+
+import cn.sciento.farm.automationv2.domain.enums.TriggerType;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 轮灌任务- 灌区VO
+ */
+@Data
+public class IrrigationTaskGroupVO {
+
+
+    @ApiModelProperty("灌区名称")
+    private String groupName;
+
+    @ApiModelProperty("灌区开始时间")
+    private LocalDateTime groupBeginTime;
+
+    @ApiModelProperty("灌区结束时间")
+    private LocalDateTime groupEndTime;
+
+    @ApiModelProperty("灌区状态")
+    private String status;
+
+    @ApiModelProperty("计划灌溉时长")
+    private Long planTime;
+
+    @ApiModelProperty("灌区内节点")
+    private List<IrrigationTaskGroupNodeVO> nodes;
+
+}

+ 50 - 0
src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskLog.java

@@ -0,0 +1,50 @@
+package cn.sciento.farm.automationv2.domain.entity.mongo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * <p>
+ *
+ * </p>
+ *
+ * @author jayHaw
+ * @date 2026/3/11:37
+ */
+@ApiModel("轮灌任务日志")
+@Document(collection = "wfauto_v2_task_log")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class IrrigationTaskLog {
+
+    @Id
+    private String id;
+
+    @ApiModelProperty("主要的数据")
+    private IrrigationTaskMainVO mainData;
+
+    @ApiModelProperty("灌区列表数据")
+    private List<IrrigationTaskGroupVO> groups;
+
+    @ApiModelProperty("任务Id")
+    private Long taskId;
+
+    @ApiModelProperty("租户Id")
+    private Long tenantId;
+
+    @ApiModelProperty("基地Id")
+    private Long organizationId;
+
+    @ApiModelProperty("任务执行实例Id")
+    private Long executionId;
+
+}

+ 50 - 0
src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskMainVO.java

@@ -0,0 +1,50 @@
+package cn.sciento.farm.automationv2.domain.entity.mongo;
+
+import cn.sciento.farm.automationv2.domain.enums.TaskStatus;
+import cn.sciento.farm.automationv2.domain.enums.TriggerType;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 轮灌任务VO
+ */
+@Data
+public class IrrigationTaskMainVO {
+
+
+    @ApiModelProperty("触发类型:SCHEDULED / LINKAGE / MANUAL")
+    private TriggerType triggerType;
+
+
+    @ApiModelProperty("任务名称")
+    private String taskName;
+
+    @ApiModelProperty("当前灌溉组名称")
+    private String groupName;
+
+    @ApiModelProperty("当前灌溉组开始时间")
+    private LocalDateTime groupBeginTime;
+
+    @ApiModelProperty("当前压力")
+    private Integer nowPressure;
+
+    @ApiModelProperty("目标压力")
+    private Integer targetPressure;
+
+    @ApiModelProperty("已灌溉时长")
+    private Long useTime;
+
+    @ApiModelProperty("计划灌溉时长")
+    private Long planTime;
+
+    @ApiModelProperty("总已灌溉时长")
+    private Long totalUseTime;
+
+    @ApiModelProperty("总计划灌溉时长")
+    private Long totalPlanTime;
+}

+ 4 - 0
src/main/java/cn/sciento/farm/automationv2/domain/service/ExecutionPlanGenerator.java

@@ -286,6 +286,9 @@ public class ExecutionPlanGenerator {
     private int addWaitNode(List<ExecutionNode> nodes, int index, Long waitSecond, String source) {
 
         Map<String, Object> params = new HashMap<>();
+        if (waitSecond <= 5){
+            waitSecond = 6L;
+        }
         params.put("seconds", waitSecond);
         params.put("source", source);
 
@@ -347,6 +350,7 @@ public class ExecutionPlanGenerator {
                         .deviceId(Long.parseLong(json.getString("deviceId")))
                         .deviceType(deviceType)
                         .deviceName(json.getString("deviceName"))
+                        .switchId(json.getLong("switchId"))
                         .build();
                 devices.add(device);
             }

+ 6 - 0
src/main/java/cn/sciento/farm/automationv2/domain/valueobject/DeviceInfo.java

@@ -6,6 +6,7 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import javax.validation.constraints.NotNull;
 import java.time.LocalDateTime;
 
 /**
@@ -34,6 +35,11 @@ public class DeviceInfo {
     private String deviceName;
 
     /**
+     * 控制节点
+     */
+    private Long switchId;
+
+    /**
      * 设备编号(网关eui)
      */
     private String eui;

+ 20 - 2
src/main/java/cn/sciento/farm/automationv2/domain/valueobject/ExecutionNode.java

@@ -1,6 +1,7 @@
 package cn.sciento.farm.automationv2.domain.valueobject;
 
 import cn.sciento.farm.automationv2.domain.enums.NodeType;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -76,10 +77,21 @@ public class ExecutionNode {
     private List<DeviceInfo> devices;
 
     /**
-     * 获取等待时长(秒)- 仅WAIT节点有效
+     *
      */
+    private Integer zoneIndex;
+
+    /**
+     *
+     */
+    private String zonePhase;
+
+    /**
+     * 获取等待时长(秒)- 仅WAIT和 ZONE_SWITCH_WAIT节点有效
+     */
+    @JsonIgnore
     public Integer getWaitSeconds() {
-        if (nodeType == NodeType.WAIT && params != null) {
+        if ((nodeType == NodeType.WAIT || nodeType == NodeType.ZONE_SWITCH_WAIT) && params != null) {
             Object seconds = params.get("seconds");
             if (seconds instanceof Integer) {
                 return (Integer) seconds;
@@ -93,6 +105,7 @@ public class ExecutionNode {
     /**
      * 获取目标压力(kPa)- 仅SET_PUMP_PRESSURE节点有效
      */
+    @JsonIgnore
     public Integer getTargetPressure() {
         if (nodeType == NodeType.SET_PUMP_PRESSURE && params != null) {
             Object pressure = params.get("pressureKpa");
@@ -108,6 +121,7 @@ public class ExecutionNode {
     /**
      * 获取球阀目标角度 - 仅OPEN_GROUP节点有效
      */
+    @JsonIgnore
     public Integer getTargetAngle() {
         if (nodeType == NodeType.OPEN_GROUP && params != null) {
             Object angle = params.get("targetAngle");
@@ -123,6 +137,7 @@ public class ExecutionNode {
     /**
      * 是否已完成
      */
+    @JsonIgnore
     public boolean isCompleted() {
         return "SUCCESS".equals(status) || "FAILED".equals(status);
     }
@@ -130,6 +145,7 @@ public class ExecutionNode {
     /**
      * 是否成功
      */
+    @JsonIgnore
     public boolean isSuccess() {
         return "SUCCESS".equals(status);
     }
@@ -137,6 +153,7 @@ public class ExecutionNode {
     /**
      * 是否失败
      */
+    @JsonIgnore
     public boolean isFailed() {
         return "FAILED".equals(status);
     }
@@ -144,6 +161,7 @@ public class ExecutionNode {
     /**
      * 是否正在执行
      */
+    @JsonIgnore
     public boolean isRunning() {
         return "RUNNING".equals(status);
     }

+ 10 - 1
src/main/java/cn/sciento/farm/automationv2/domain/valueobject/ExecutionPlan.java

@@ -1,5 +1,6 @@
 package cn.sciento.farm.automationv2.domain.valueobject;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -26,6 +27,7 @@ public class ExecutionPlan {
     /**
      * 获取指定索引的节点
      */
+    @JsonIgnore
     public ExecutionNode getNode(int index) {
         if (index < 0 || index >= nodes.size()) {
             return null;
@@ -36,6 +38,7 @@ public class ExecutionPlan {
     /**
      * 获取节点总数
      */
+    @JsonIgnore
     public int getNodeCount() {
         return nodes != null ? nodes.size() : 0;
     }
@@ -43,6 +46,7 @@ public class ExecutionPlan {
     /**
      * 是否还有下一个节点
      */
+    @JsonIgnore
     public boolean hasNext(int currentIndex) {
         return currentIndex + 1 < getNodeCount();
     }
@@ -50,6 +54,7 @@ public class ExecutionPlan {
     /**
      * 获取下一个节点
      */
+    @JsonIgnore
     public ExecutionNode getNextNode(int currentIndex) {
         return getNode(currentIndex + 1);
     }
@@ -57,6 +62,7 @@ public class ExecutionPlan {
     /**
      * 获取已完成的节点列表
      */
+    @JsonIgnore
     public List<ExecutionNode> getCompletedNodes() {
         if (nodes == null) {
             return null;
@@ -69,6 +75,7 @@ public class ExecutionPlan {
     /**
      * 获取已成功的节点列表
      */
+    @JsonIgnore
     public List<ExecutionNode> getSuccessNodes() {
         if (nodes == null) {
             return null;
@@ -81,6 +88,7 @@ public class ExecutionPlan {
     /**
      * 获取失败的节点列表
      */
+    @JsonIgnore
     public List<ExecutionNode> getFailedNodes() {
         if (nodes == null) {
             return null;
@@ -93,6 +101,7 @@ public class ExecutionPlan {
     /**
      * 计算预计总时长(分钟)
      */
+    @JsonIgnore
     public int calculateExpectedDurationMinutes() {
         if (nodes == null) {
             return 0;
@@ -102,6 +111,6 @@ public class ExecutionPlan {
                 .filter(node -> node.getNodeType() != null)
                 .mapToInt(ExecutionNode::getWaitSeconds)
                 .sum();
-        return (totalSeconds / 60) + 5; // 额外加5分钟作为设备响应和切换时间
+        return totalSeconds  + 5; // 额外加5分钟作为设备响应和切换时间
     }
 }

+ 9 - 1
src/main/java/cn/sciento/farm/automationv2/infra/feign/DeviceFeign.java

@@ -35,5 +35,13 @@ public interface DeviceFeign {
     ResponseEntity<String> control(@PathVariable(value = "tenantId") Long tenantId,
                                    @RequestBody GatewayControlDto dto);
 
-
+    /**
+     * feign控制变频器
+     */
+    @RequestMapping(
+            value = "/v1/{tenantId}/inverter/config",
+            method = RequestMethod.POST,
+            consumes = "application/json")
+    ResponseEntity<String> PumpPressure(@PathVariable(value = "tenantId") Long tenantId,
+                                  @RequestBody GatewayControlDto dto);
 }

+ 5 - 0
src/main/java/cn/sciento/farm/automationv2/infra/feign/fallback/DeviceFallback.java

@@ -29,6 +29,11 @@ public class DeviceFallback implements FallbackFactory<DeviceFeign> {
                 throw new CommonException(throwable);
             }
 
+            @Override
+            public ResponseEntity<String> PumpPressure(Long tenantId, GatewayControlDto dto) {
+                throw new CommonException(throwable);
+            }
+
         };
     }
 }

+ 5 - 5
src/main/java/cn/sciento/farm/automationv2/infra/mq/consumer/CheckAckConsumer.java

@@ -42,11 +42,11 @@ public class CheckAckConsumer implements RocketMQListener<CheckAckMessage> {
                 executionId, nodeIndex, retryCount, message.getMessageId());
 
         // 幂等性检查:如果节点已推进,忽略CHECK_ACK消息
-        if (idempotencyManager.isProceed(executionId, nodeIndex)) {
-            log.info("节点已推进,忽略CHECK_ACK消息,executionId={}, nodeIndex={}, messageId={}",
-                    executionId, nodeIndex, message.getMessageId());
-            return; // 幂等,不重复处理
-        }
+//        if (idempotencyManager.isProceed(executionId, nodeIndex)) {
+//            log.info("节点已推进,忽略CHECK_ACK消息,executionId={}, nodeIndex={}, messageId={}",
+//                    executionId, nodeIndex, message.getMessageId());
+//            return; // 幂等,不重复处理
+//        }
 
         try {
             taskExecutionEngine.checkAckAndProceed(executionId, nodeIndex);

+ 13 - 10
src/main/java/cn/sciento/farm/automationv2/infra/mq/producer/FlowControlProducer.java

@@ -66,11 +66,11 @@ public class FlowControlProducer {
 
     /**
      * 发送推进到下一节点消息
-     * 说明:在WAIT节点等待时长后发送,支持分钟级延迟
+     * 说明:在WAIT节点等待时长后发送,支持级延迟
      *
-     * @param delayMinutes 延迟分钟数(0表示立即发送)
+     * @param delaySecond 延迟秒数(0表示立即发送)
      */
-    public void sendNextNode(Long executionId, Integer currentNodeIndex, int delayMinutes) {
+    public void sendNextNode(Long executionId, Integer currentNodeIndex, int delaySecond) {
         NextNodeMessage message = NextNodeMessage.builder()
                 .executionId(executionId)
                 .currentNodeIndex(currentNodeIndex)
@@ -82,24 +82,27 @@ public class FlowControlProducer {
 
         try {
             SendResult sendResult;
-            if (delayMinutes == 0) {
+            if (delaySecond == 0) {
                 // 立即发送
                 sendResult = rocketMQTemplate.syncSend(destination, message);
             } else {
+                // 以防快速发送,导致wait节点重复消费 + 卡住
+                if (delaySecond <= 5){
+                    delaySecond = 6;
+                }
                 // 延迟发送(分钟级)
                 // RocketMQ延迟级别最大18(2小时),超过需要使用自定义延迟
-                int delaySeconds = delayMinutes * 60;
-                int delayLevel = calculateDelayLevel(delaySeconds);
+                int delayLevel = calculateDelayLevel(delaySecond);
 
                 Message<NextNodeMessage> msg = MessageBuilder.withPayload(message).build();
                 sendResult = rocketMQTemplate.syncSend(destination, msg, 3000, delayLevel);
             }
 
-            log.info("发送推进下一节点消息成功,executionId={}, currentNodeIndex={}, delayMinutes={}, messageId={}, sendResult={}",
-                    executionId, currentNodeIndex, delayMinutes, message.getMessageId(), sendResult.getMsgId());
+            log.info("发送推进下一节点消息成功,executionId={}, currentNodeIndex={}, delaySecond={}, messageId={}, sendResult={}",
+                    executionId, currentNodeIndex, delaySecond, message.getMessageId(), sendResult.getMsgId());
         } catch (Exception e) {
-            log.error("发送推进下一节点消息失败,executionId={}, currentNodeIndex={}, delayMinutes={}, messageId={}",
-                    executionId, currentNodeIndex, delayMinutes, message.getMessageId(), e);
+            log.error("发送推进下一节点消息失败,executionId={}, currentNodeIndex={}, delaySecond={}, messageId={}",
+                    executionId, currentNodeIndex, delaySecond, message.getMessageId(), e);
             throw new RuntimeException("发送推进下一节点消息失败", e);
         }
     }

+ 30 - 0
src/main/resources/application.yml

@@ -46,6 +46,36 @@ spring:
         # 默认 -1 表示永不超时,设置5秒
         max-wait: ${SPRING_REDIS_POOL_MAX_WAIT:5000}
 
+feign:
+  hystrix:
+    enabled: true
+
+hystrix:
+  threadpool:
+    default:
+      # 执行命令线程池的核心线程数,也是命令执行的最大并发量
+      # 默认10
+      coreSize: 1000
+      # 最大执行线程数
+      maximumSize: 1000
+  command:
+    default:
+      execution:
+        isolation:
+          thread:
+            # HystrixCommand 执行的超时时间,超时后进入降级处理逻辑。一个接口,理论的最佳响应速度应该在200ms以内,或者慢点的接口就几百毫秒。
+            # 默认 1000 毫秒,最高设置 2000足矣。如果超时,首先看能不能优化接口相关业务、SQL查询等,不要盲目加大超时时间,否则会导致线程堆积过多,hystrix 线程池卡死,最终服务不可用。
+            timeoutInMilliseconds: ${HYSTRIX_COMMAND_TIMEOUT_IN_MILLISECONDS:40000}
+
+ribbon:
+  # 客户端读取超时时间,超时时间要小于Hystrix的超时时间,否则重试机制就无意义了
+  ReadTimeout: ${RIBBON_READ_TIMEOUT:30000}
+  # 客户端连接超时时间
+  ConnectTimeout: ${RIBBON_CONNECT_TIMEOUT:3000}
+  # 访问实例失败(超时),允许自动重试,设置重试次数,失败后会更换实例访问,请一定确保接口的幂等性,否则重试可能导致数据异常。
+  OkToRetryOnAllOperations: true
+  MaxAutoRetries: 1
+  MaxAutoRetriesNextServer: 1
 
 
   # Quartz 调度器配置(集群模式)

+ 2 - 2
src/main/resources/bootstrap.yml

@@ -18,9 +18,9 @@ spring:
     nacos:
       config:
         server-addr: 192.168.10.18:8848
-        namespace: stong-test-train
+        namespace: stong-test-demo1
       discovery:
         server-addr: 192.168.10.18:8848
-        namespace: stong-test-train
+        namespace: stong-test-demo1
         metadata:
           VERSION: 1.0.0