From c6d75c0d50859fbc68eaea05d49d8e4738ad525d Mon Sep 17 00:00:00 2001
From: vincentlu <t1341870251@gmail.com>
Date: 星期五, 27 二月 2026 16:01:40 +0800
Subject: [PATCH] #

---
 zy-acs-manager/src/main/java/com/zy/acs/manager/core/service/GuaranteeRuntimeService.java |  117 ++++++++++++-----------------
 zy-acs-manager/src/main/java/com/zy/acs/manager/core/scheduler/GuaranteeScheduler.java    |  110 +++------------------------
 2 files changed, 63 insertions(+), 164 deletions(-)

diff --git a/zy-acs-manager/src/main/java/com/zy/acs/manager/core/scheduler/GuaranteeScheduler.java b/zy-acs-manager/src/main/java/com/zy/acs/manager/core/scheduler/GuaranteeScheduler.java
index 243666f..7440d7f 100644
--- a/zy-acs-manager/src/main/java/com/zy/acs/manager/core/scheduler/GuaranteeScheduler.java
+++ b/zy-acs-manager/src/main/java/com/zy/acs/manager/core/scheduler/GuaranteeScheduler.java
@@ -5,7 +5,6 @@
 import com.zy.acs.manager.manager.entity.Guarantee;
 import com.zy.acs.manager.manager.enums.StatusType;
 import com.zy.acs.manager.manager.service.GuaranteeService;
-import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.scheduling.annotation.Scheduled;
@@ -14,13 +13,9 @@
 
 import java.time.LocalDateTime;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
 
 @Slf4j
-//@Component
+@Component
 public class GuaranteeScheduler {
 
     private final GuaranteeService guaranteeService;
@@ -30,12 +25,6 @@
     private boolean enabled;
     @Value("${guarantee.scheduler.default-lead-minutes:60}")
     private int defaultLeadMinutes;
-    @Value("${guarantee.scheduler.lock-duration-minutes:5}")
-    private int lockDurationMinutes;
-    @Value("${guarantee.scheduler.window-duration-minutes:10}")
-    private int windowDurationMinutes;
-
-    private final Map<Long, PlanRuntimeState> runtimeStates = new ConcurrentHashMap<>();
 
     public GuaranteeScheduler(GuaranteeService guaranteeService,
                               GuaranteeRuntimeService guaranteeRuntimeService) {
@@ -43,14 +32,14 @@
         this.guaranteeRuntimeService = guaranteeRuntimeService;
     }
 
-    @Scheduled(cron = "0/15 * * * * ?")
+    @Scheduled(cron = "0/1 * * * * ?")
     public void drive() {
         if (!enabled) {
             return;
         }
         List<Guarantee> plans = guaranteeService.list(new LambdaQueryWrapper<Guarantee>()
+                .eq(Guarantee::getDeleted, 0)
                 .eq(Guarantee::getStatus, StatusType.ENABLE.val));
-        syncCache(plans);
         LocalDateTime now = LocalDateTime.now();
         for (Guarantee plan : plans) {
             try {
@@ -62,104 +51,33 @@
     }
 
     private void processPlan(Guarantee plan, LocalDateTime now) {
-        PlanRuntimeState state = runtimeStates.computeIfAbsent(plan.getId(), id -> new PlanRuntimeState());
-        if (state.getTargetTime() == null || now.isAfter(state.getWindowEnd())) {
-            LocalDateTime next = computeNext(plan, now);
-            if (next == null) {
-                runtimeStates.remove(plan.getId());
-                return;
-            }
-            state.init(plan, next, resolveLeadMinutes(plan));
-        }
-        if (now.isBefore(state.getPrepareStart())) {
-            state.setPhase(Phase.IDLE);
+        LocalDateTime targetTime = resolveNextTarget(plan, now);
+        if (targetTime == null) {
             return;
         }
-        if (now.isBefore(state.getLockStart())) {
-            state.transition(Phase.PREPARING, () -> guaranteeRuntimeService.prepare(plan, state.getTargetTime()));
+        int leadMinutes = resolveLeadMinutes(plan);
+        LocalDateTime prepareFrom = targetTime.minusMinutes(leadMinutes);
+        if (now.isBefore(prepareFrom) || now.isAfter(targetTime)) {
             return;
         }
-        if (now.isBefore(state.getTargetTime())) {
-            state.transition(Phase.LOCKING, () -> guaranteeRuntimeService.lock(plan, state.getTargetTime()));
-            return;
-        }
-        if (now.isBefore(state.getWindowEnd())) {
-            state.transition(Phase.WINDOW, () -> guaranteeRuntimeService.checkWindow(plan, state.getTargetTime()));
-            guaranteeRuntimeService.checkWindow(plan, state.getTargetTime());
-            return;
-        }
-        if (state.getPhase() != Phase.FINISHED) {
-            state.transition(Phase.FINISHED, () -> guaranteeRuntimeService.finish(plan, state.getTargetTime()));
-        }
-        LocalDateTime next = computeNext(plan, state.getWindowEnd().plusSeconds(1));
-        if (next == null) {
-            runtimeStates.remove(plan.getId());
-            return;
-        }
-        state.init(plan, next, resolveLeadMinutes(plan));
+        guaranteeRuntimeService.prepare(plan, targetTime);
     }
 
-    private void syncCache(List<Guarantee> plans) {
-        Set<Long> activeIds = plans.stream().map(Guarantee::getId).collect(Collectors.toSet());
-        runtimeStates.keySet().removeIf(id -> !activeIds.contains(id));
-    }
-
-    private int resolveLeadMinutes(Guarantee plan) {
-        return Math.max(1, plan.getLeadTime() == null ? defaultLeadMinutes : plan.getLeadTime());
-    }
-
-    private LocalDateTime computeNext(Guarantee plan, LocalDateTime reference) {
+    private LocalDateTime resolveNextTarget(Guarantee plan, LocalDateTime now) {
         String cronExpr = plan.getCronExpr();
         if (cronExpr == null || cronExpr.trim().isEmpty()) {
             return null;
         }
         try {
             CronExpression cron = CronExpression.parse(cronExpr);
-            LocalDateTime base = reference == null ? LocalDateTime.now() : reference;
-            LocalDateTime next = cron.next(base);
-            if (next == null && !base.equals(LocalDateTime.now())) {
-                next = cron.next(LocalDateTime.now());
-            }
-            return next;
+            return cron.next(now.minusSeconds(1));  // next(2026-02-27 08:15) =>  2026-02-27 10:00:00
         } catch (IllegalArgumentException ex) {
-            log.error("Guarantee[{}] invalid cron {} ", plan.getName(), cronExpr, ex);
+            log.error("Guarantee[{}] invalid cron {}", plan.getName(), cronExpr, ex);
             return null;
         }
     }
 
-    private enum Phase {
-        IDLE,
-        PREPARING,
-        LOCKING,
-        WINDOW,
-        FINISHED
-    }
-
-    @Data
-    private class PlanRuntimeState {
-        private LocalDateTime targetTime;
-        private LocalDateTime prepareStart;
-        private LocalDateTime lockStart;
-        private LocalDateTime windowEnd;
-        private Phase phase = Phase.IDLE;
-
-        private void init(Guarantee plan, LocalDateTime target, int leadMinutes) {
-            this.targetTime = target;
-            this.prepareStart = target.minusMinutes(Math.max(1, leadMinutes));
-            int lockMinutes = Math.min(lockDurationMinutes, leadMinutes);
-            this.lockStart = target.minusMinutes(Math.max(1, lockMinutes));
-            this.windowEnd = target.plusMinutes(Math.max(1, windowDurationMinutes));
-            this.phase = Phase.IDLE;
-            log.info("Guarantee[{}] next target {} prepare@{} lock@{} windowEnd@{}",
-                    plan.getName(), targetTime, prepareStart, lockStart, windowEnd);
-        }
-
-        private void transition(Phase targetPhase, Runnable callback) {
-            if (this.phase.ordinal() >= targetPhase.ordinal()) {
-                return;
-            }
-            callback.run();
-            this.phase = targetPhase;
-        }
+    private int resolveLeadMinutes(Guarantee plan) {
+        return Math.max(1, plan.getLeadTime() == null ? defaultLeadMinutes : plan.getLeadTime());
     }
 }
diff --git a/zy-acs-manager/src/main/java/com/zy/acs/manager/core/service/GuaranteeRuntimeService.java b/zy-acs-manager/src/main/java/com/zy/acs/manager/core/service/GuaranteeRuntimeService.java
index 0a38972..1817ed4 100644
--- a/zy-acs-manager/src/main/java/com/zy/acs/manager/core/service/GuaranteeRuntimeService.java
+++ b/zy-acs-manager/src/main/java/com/zy/acs/manager/core/service/GuaranteeRuntimeService.java
@@ -12,8 +12,6 @@
 import com.zy.acs.manager.manager.service.AgvDetailService;
 import com.zy.acs.manager.manager.service.AgvService;
 import com.zy.acs.manager.manager.service.TaskService;
-import lombok.AllArgsConstructor;
-import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
@@ -22,7 +20,6 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
-import java.util.Optional;
 
 @Slf4j
 @Service
@@ -44,40 +41,12 @@
     }
 
     public void prepare(Guarantee plan, LocalDateTime targetTime) {
-        CapacitySnapshot snapshot = evaluate(plan);
-        if (snapshot.availableCount >= plan.getRequiredCount()) {
-            log.debug("Guarantee[{}] already has {} available vehicles for {}",
-                    plan.getName(), snapshot.availableCount, targetTime);
+        int requiredCount = plan.getRequiredCount() == null ? 0 : plan.getRequiredCount();
+        if (requiredCount <= 0) {
+            log.warn("Guarantee[{}] requiredCount is not configured, skip", plan.getName());
             return;
         }
-        int shortage = plan.getRequiredCount() - snapshot.availableCount;
-        log.info("Guarantee[{}] shortage {} vehicles for {} (minSoc={}%), scheduling charge.",
-                plan.getName(), shortage, targetTime, plan.getMinSoc());
-        dispatchChargeTasks(snapshot, shortage);
-    }
-
-    public void lock(Guarantee plan, LocalDateTime targetTime) {
-        log.info("Guarantee[{}] entering lock stage for {}", plan.getName(), targetTime);
-        // TODO persist lock state / inform dispatcher once integration is ready
-    }
-
-    public void checkWindow(Guarantee plan, LocalDateTime targetTime) {
-        CapacitySnapshot snapshot = evaluate(plan);
-        if (snapshot.availableCount < plan.getRequiredCount()) {
-            int shortage = plan.getRequiredCount() - snapshot.availableCount;
-            log.warn("Guarantee[{}] window shortage {} vehicles for {}. Trigger quick recharge.",
-                    plan.getName(), shortage, targetTime);
-            dispatchChargeTasks(snapshot, shortage);
-        }
-    }
-
-    public void finish(Guarantee plan, LocalDateTime targetTime) {
-        log.info("Guarantee[{}] finished window for {}", plan.getName(), targetTime);
-        // TODO release any lock/flag once detail implementation is defined
-    }
-
-    private CapacitySnapshot evaluate(Guarantee plan) {
-        int minSoc = Optional.ofNullable(plan.getMinSoc()).orElse(50);
+        int minSoc = plan.getMinSoc() == null ? 50 : plan.getMinSoc();
         List<Agv> scopedAgvs = findScopedAgvs(plan);
         int available = 0;
         List<ChargeCandidate> candidates = new ArrayList<>();
@@ -86,45 +55,52 @@
             if (detail == null || detail.getSoc() == null) {
                 continue;
             }
-            if (detail.getSoc() >= minSoc && isAvailable(agv, detail)) {
+            if (!isIdle(agv, detail)) {
+                continue;
+            }
+            int soc = detail.getSoc();
+            if (soc >= minSoc) {
                 available++;
             } else {
-                candidates.add(new ChargeCandidate(agv, detail));
+                candidates.add(new ChargeCandidate(agv, soc));
             }
         }
-        candidates.sort(Comparator.comparingInt(o -> Optional.ofNullable(o.detail.getSoc()).orElse(0)));
-        return new CapacitySnapshot(available, candidates);
+        if (available >= requiredCount) {
+            log.debug("Guarantee[{}] already has {} vehicles >= {}% SOC for {}", plan.getName(), available, minSoc, targetTime);
+            return;
+        }
+        int shortage = requiredCount - available;
+        candidates.sort(Comparator.comparingInt(ChargeCandidate::getSoc));
+        int scheduled = 0;
+        for (ChargeCandidate candidate : candidates) {
+            if (scheduled >= shortage) {
+                break;
+            }
+            log.info("Guarantee[{}] schedule AGV {} charging (soc={}%) for target {}",
+                    plan.getName(), candidate.getAgv().getName(), candidate.getSoc(), targetTime);
+            mainLockWrapService.buildMinorTask(candidate.getAgv().getId(), TaskTypeType.TO_CHARGE, null, null);
+            scheduled++;
+        }
+        if (scheduled < shortage) {
+            log.warn("Guarantee[{}] still short of {} vehicles for {} (only {} idle low-soc AGVs)",
+                    plan.getName(), shortage - scheduled, targetTime, candidates.size());
+        }
     }
 
-    private boolean isAvailable(Agv agv, AgvDetail detail) {
+    private boolean isIdle(Agv agv, AgvDetail detail) {
         if (!Objects.equals(agv.getStatus(), StatusType.ENABLE.val)) {
             return false;
         }
         if (detail.getAgvStatus() != null && detail.getAgvStatus().equals(AgvStatusType.CHARGE)) {
             return false;
         }
-        boolean busy = taskService.count(new LambdaQueryWrapper<Task>()
+        long busyCount = taskService.count(new LambdaQueryWrapper<Task>()
                 .eq(Task::getAgvId, agv.getId())
                 .in(Task::getTaskSts,
                         TaskStsType.WAITING.val(),
                         TaskStsType.ASSIGN.val(),
-                        TaskStsType.PROGRESS.val())) > 0;
-        return !busy;
-    }
-
-    private void dispatchChargeTasks(CapacitySnapshot snapshot, int shortage) {
-        if (shortage <= 0) {
-            return;
-        }
-        int scheduled = 0;
-        for (ChargeCandidate candidate : snapshot.candidates) {
-            if (scheduled >= shortage) {
-                break;
-            }
-            log.info("Scheduling AGV [{}] for charging to support guarantee", candidate.agv.getName());
-            mainLockWrapService.buildMinorTask(candidate.agv.getId(), TaskTypeType.TO_CHARGE, null, null);
-            scheduled++;
-        }
+                        TaskStsType.PROGRESS.val()));
+        return busyCount == 0;
     }
 
     private List<Agv> findScopedAgvs(Guarantee plan) {
@@ -140,16 +116,21 @@
         return agvService.list(wrapper);
     }
 
-    @Data
-    private static class CapacitySnapshot {
-        private final int availableCount;
-        private final List<ChargeCandidate> candidates;
-    }
-
-    @Data
-    @AllArgsConstructor
     private static class ChargeCandidate {
-        private Agv agv;
-        private AgvDetail detail;
+        private final Agv agv;
+        private final int soc;
+
+        ChargeCandidate(Agv agv, int soc) {
+            this.agv = agv;
+            this.soc = soc;
+        }
+
+        public Agv getAgv() {
+            return agv;
+        }
+
+        public int getSoc() {
+            return soc;
+        }
     }
 }

--
Gitblit v1.9.1