From 9a8018c3fbc94f99d5d184c8cb1ef23d7366cea0 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 29 四月 2026 17:02:38 +0800
Subject: [PATCH] #堆垛机任务执行优先级修改

---
 src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java |  893 ++++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 750 insertions(+), 143 deletions(-)

diff --git a/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java b/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
index 3143610..6ae0467 100644
--- a/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
+++ b/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
@@ -5,6 +5,7 @@
 import com.zy.ai.domain.autotune.AutoTuneApplyRequest;
 import com.zy.ai.domain.autotune.AutoTuneApplyResult;
 import com.zy.ai.domain.autotune.AutoTuneChangeCommand;
+import com.zy.ai.domain.autotune.AutoTuneControlModeSnapshot;
 import com.zy.ai.domain.autotune.AutoTuneJobStatus;
 import com.zy.ai.domain.autotune.AutoTuneRuleDefinition;
 import com.zy.ai.domain.autotune.AutoTuneTargetType;
@@ -14,30 +15,52 @@
 import com.zy.ai.service.AiAutoTuneChangeService;
 import com.zy.ai.service.AiAutoTuneJobService;
 import com.zy.ai.service.AutoTuneApplyService;
+import com.zy.ai.service.AutoTuneControlModeService;
 import com.zy.asrs.entity.BasCrnp;
 import com.zy.asrs.entity.BasDualCrnp;
 import com.zy.asrs.entity.BasStation;
-import com.zy.asrs.entity.StationFlowCapacity;
+import com.zy.asrs.entity.WrkMast;
 import com.zy.asrs.service.BasCrnpService;
 import com.zy.asrs.service.BasDualCrnpService;
 import com.zy.asrs.service.BasStationService;
-import com.zy.asrs.service.StationFlowCapacityService;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.enums.WrkStsType;
 import com.zy.system.entity.Config;
 import com.zy.system.service.ConfigService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.support.TransactionTemplate;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
 
 @Service("autoTuneApplyService")
 public class AutoTuneApplyServiceImpl implements AutoTuneApplyService {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(AutoTuneApplyServiceImpl.class);
     private static final String PROMPT_SCENE_CODE = "auto_tune_apply";
-    private static final String DIRECTION_OUT = "OUT";
+    private static final long APPLY_LOCK_SECONDS = 120L;
+    private static final String APPLY_LOCK_BUSY_REASON = "鐢宠璋冨弬閿佸け璐ワ紝閿佷笉鍙敤锛屽彲鑳藉凡鏈変换鍔℃垨 Redis 寮傚父";
+    private static final String ANALYSIS_ONLY_REJECT_REASON = "浠呭垎鏋愭ā寮忕姝㈠疄闄呭簲鐢�/鍥炴粴锛屾湭淇敼杩愯鍙傛暟";
+    private static final List<Long> FINAL_WRK_STS_LIST = Arrays.asList(
+            WrkStsType.COMPLETE_INBOUND.sts,
+            WrkStsType.SETTLE_INBOUND.sts,
+            WrkStsType.COMPLETE_OUTBOUND.sts,
+            WrkStsType.SETTLE_OUTBOUND.sts,
+            WrkStsType.COMPLETE_LOC_MOVE.sts,
+            WrkStsType.COMPLETE_CRN_MOVE.sts
+    );
 
     @Autowired
     private AiAutoTuneJobService aiAutoTuneJobService;
@@ -46,90 +69,236 @@
     @Autowired
     private ConfigService configService;
     @Autowired
+    private AutoTuneControlModeService autoTuneControlModeService;
+    @Autowired
     private BasStationService basStationService;
     @Autowired
     private BasCrnpService basCrnpService;
     @Autowired
     private BasDualCrnpService basDualCrnpService;
     @Autowired
-    private StationFlowCapacityService stationFlowCapacityService;
+    private WrkMastService wrkMastService;
+    @Autowired
+    private PlatformTransactionManager transactionManager;
+    @Autowired
+    private RedisUtil redisUtil;
 
     @Override
-    @Transactional
     public AutoTuneApplyResult apply(AutoTuneApplyRequest request) {
         AutoTuneApplyRequest safeRequest = request == null ? new AutoTuneApplyRequest() : request;
+        AutoTuneControlModeSnapshot controlMode = currentControlModeSnapshot();
         boolean dryRun = Boolean.TRUE.equals(safeRequest.getDryRun());
         Date now = new Date();
         AiAutoTuneJob job = createJob(safeRequest, dryRun, now);
-        aiAutoTuneJobService.save(job);
 
-        List<ValidatedChange> validatedChanges = validateChanges(safeRequest, dryRun, now);
-        boolean hasRejectedChange = hasRejectedChange(validatedChanges);
-        if (!dryRun && hasRejectedChange) {
-            markAcceptedChangesAsBatchRejected(validatedChanges);
+        if (dryRun) {
+            return applyDryRun(safeRequest, job, now, controlMode);
         }
-        if (!dryRun && !hasRejectedChange) {
-            applyValidatedChanges(validatedChanges);
+        if (isAnalysisOnly(controlMode)) {
+            return rejectRealApplyForAnalysisOnly(safeRequest, job, now, controlMode);
+        }
+        return applyRealWithLock(safeRequest, job, now, controlMode);
+    }
+
+    private AutoTuneApplyResult applyDryRun(AutoTuneApplyRequest request,
+                                            AiAutoTuneJob job,
+                                            Date now,
+                                            AutoTuneControlModeSnapshot controlMode) {
+        List<ValidatedChange> validatedChanges = validateChanges(request, true, now);
+        ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+                job,
+                request,
+                validatedChanges,
+                true,
+                now,
+                false
+        );
+        return buildResult(job, persistenceResult.getAuditChanges(), true, controlMode);
+    }
+
+    private AutoTuneApplyResult applyRealWithLock(AutoTuneApplyRequest request,
+                                                  AiAutoTuneJob job,
+                                                  Date now,
+                                                  AutoTuneControlModeSnapshot controlMode) {
+        if (request.getChanges() == null || request.getChanges().isEmpty()) {
+            ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+                    job,
+                    request,
+                    new ArrayList<>(),
+                    false,
+                    now,
+                    false
+            );
+            return buildResult(job, persistenceResult.getAuditChanges(), false, controlMode);
         }
 
-        List<AiAutoTuneChange> auditChanges = buildAuditChanges(job.getId(), validatedChanges, now);
-        if (!auditChanges.isEmpty()) {
-            aiAutoTuneChangeService.saveBatch(auditChanges);
+        String lockKey = RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key;
+        String lockToken = UUID.randomUUID().toString();
+        if (!redisUtil.trySetStringIfAbsent(lockKey, lockToken, APPLY_LOCK_SECONDS)) {
+            return rejectRealApplyForUnavailableLock(request, job, now, lockKey, controlMode);
         }
-        finishJob(job, safeRequest, auditChanges, dryRun, now);
-        aiAutoTuneJobService.updateById(job);
-        return buildResult(job, auditChanges, dryRun);
+
+        try {
+            List<ValidatedChange> validatedChanges = validateChanges(request, false, now);
+            boolean hasRejectedChange = hasRejectedChange(validatedChanges);
+            if (hasRejectedChange) {
+                markAcceptedChangesAsBatchRejected(validatedChanges);
+            }
+            if (hasRejectedChange) {
+                ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+                        job,
+                        request,
+                        validatedChanges,
+                        false,
+                        now,
+                        false
+                );
+                return buildResult(job, persistenceResult.getAuditChanges(), false, controlMode);
+            }
+
+            try {
+                ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+                        job,
+                        request,
+                        validatedChanges,
+                        false,
+                        now,
+                        true
+                );
+                refreshSystemConfigCacheSafely(persistenceResult);
+                return buildResult(job, persistenceResult.getAuditChanges(), false, controlMode);
+            } catch (RuntimeException exception) {
+                markWriteFailure(validatedChanges, exception);
+                Date failureNow = new Date();
+                AiAutoTuneJob failureJob = createJob(request, false, failureNow);
+                ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+                        failureJob,
+                        request,
+                        validatedChanges,
+                        false,
+                        failureNow,
+                        false
+                );
+                return buildResult(failureJob, persistenceResult.getAuditChanges(), false, controlMode);
+            }
+        } finally {
+            redisUtil.compareAndDelete(lockKey, lockToken);
+        }
+    }
+
+    private AutoTuneApplyResult rejectRealApplyForUnavailableLock(AutoTuneApplyRequest request,
+                                                                  AiAutoTuneJob job,
+                                                                  Date now,
+                                                                  String lockKey,
+                                                                  AutoTuneControlModeSnapshot controlMode) {
+        boolean lockKeyExists = redisUtil.hasKey(lockKey);
+        LOGGER.warn("鐢宠AI鑷姩璋冨弬 apply 閿佸け璐ワ紝lockKey={}, lockKeyExists={}", lockKey, lockKeyExists);
+        List<ValidatedChange> validatedChanges = buildLockBusyChanges(request);
+        ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+                job,
+                request,
+                validatedChanges,
+                false,
+                now,
+                false
+        );
+        return buildResult(job, persistenceResult.getAuditChanges(), false, controlMode);
+    }
+
+    private AutoTuneApplyResult rejectRealApplyForAnalysisOnly(AutoTuneApplyRequest request,
+                                                               AiAutoTuneJob job,
+                                                               Date now,
+                                                               AutoTuneControlModeSnapshot controlMode) {
+        ApplyPersistenceResult persistenceResult = persistAnalysisOnlyApplyRejectionInTransaction(job, request, now);
+        AutoTuneApplyResult result = buildResult(job, persistenceResult.getAuditChanges(), false, controlMode);
+        result.setAnalysisOnly(true);
+        result.setNoApply(true);
+        return result;
+    }
+
+    private List<ValidatedChange> buildLockBusyChanges(AutoTuneApplyRequest request) {
+        List<ValidatedChange> validatedChanges = new ArrayList<>();
+        if (request.getChanges() == null || request.getChanges().isEmpty()) {
+            return validatedChanges;
+        }
+        for (AutoTuneChangeCommand command : request.getChanges()) {
+            ValidatedChange validatedChange = new ValidatedChange(command);
+            validatedChange.fail(APPLY_LOCK_BUSY_REASON);
+            validatedChanges.add(validatedChange);
+        }
+        return validatedChanges;
     }
 
     @Override
-    @Transactional
     public AutoTuneApplyResult rollbackLastSuccessfulJob(String reason) {
+        AutoTuneControlModeSnapshot controlMode = currentControlModeSnapshot();
         Date now = new Date();
         AiAutoTuneJob rollbackJob = createRollbackJob(reason, now);
-        aiAutoTuneJobService.save(rollbackJob);
-
-        List<AiAutoTuneChange> sourceChanges = findLatestSuccessfulChanges();
-        if (sourceChanges.isEmpty()) {
-            rollbackJob.setStatus(AutoTuneJobStatus.REJECTED.getCode());
-            rollbackJob.setFinishTime(now);
-            rollbackJob.setRejectCount(0);
-            rollbackJob.setSuccessCount(0);
-            rollbackJob.setSummary("鏈壘鍒板彲鍥炴粴鐨勬垚鍔熻皟鍙傝褰�");
-            rollbackJob.setErrorMessage("鏈壘鍒板彲鍥炴粴鐨勬垚鍔熻皟鍙傝褰�");
-            aiAutoTuneJobService.updateById(rollbackJob);
-            return buildResult(rollbackJob, new ArrayList<>(), false);
+        if (isAnalysisOnly(controlMode)) {
+            return rejectRollbackForAnalysisOnly(rollbackJob, now, controlMode);
         }
 
-        List<AiAutoTuneChange> rollbackChanges = new ArrayList<>();
-        boolean refreshedConfigCache = false;
-        for (AiAutoTuneChange sourceChange : sourceChanges) {
-            AiAutoTuneChange rollbackChange = buildRollbackChange(rollbackJob.getId(), sourceChange, now);
-            try {
-                String currentValue = readCurrentValue(
-                        sourceChange.getTargetType(),
-                        sourceChange.getTargetId(),
-                        sourceChange.getTargetKey()
-                );
-                rollbackChange.setOldValue(currentValue);
-                writeValue(sourceChange.getTargetType(), sourceChange.getTargetId(), sourceChange.getTargetKey(), sourceChange.getOldValue());
-                if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(sourceChange.getTargetType())) {
-                    refreshedConfigCache = true;
-                }
-                rollbackChange.setAppliedValue(sourceChange.getOldValue());
-                rollbackChange.setResultStatus(ChangeStatus.SUCCESS.getCode());
-            } catch (Exception exception) {
-                rollbackChange.setResultStatus(ChangeStatus.FAILED.getCode());
-                rollbackChange.setRejectReason(exception.getMessage());
+        String lockKey = RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key;
+        String lockToken = UUID.randomUUID().toString();
+        if (!redisUtil.trySetStringIfAbsent(lockKey, lockToken, APPLY_LOCK_SECONDS)) {
+            return rejectRollbackForUnavailableLock(reason, now, lockKey, controlMode);
+        }
+
+        try {
+            List<AiAutoTuneChange> sourceChanges = findLatestSuccessfulChanges();
+            if (sourceChanges.isEmpty()) {
+                persistNoRollbackSourceJobInTransaction(rollbackJob, now);
+                return buildResult(rollbackJob, new ArrayList<>(), false, controlMode);
             }
-            rollbackChanges.add(rollbackChange);
+            try {
+                RollbackPersistenceResult persistenceResult = persistRollbackResultInTransaction(
+                        rollbackJob,
+                        sourceChanges,
+                        now
+                );
+                refreshRollbackConfigCacheSafely(persistenceResult);
+                return buildResult(rollbackJob, persistenceResult.getRollbackChanges(), false, controlMode);
+            } catch (RuntimeException exception) {
+                Date failureNow = new Date();
+                AiAutoTuneJob failureJob = createRollbackJob(reason, failureNow);
+                RollbackPersistenceResult persistenceResult = persistFailedRollbackResultInTransaction(
+                        failureJob,
+                        sourceChanges,
+                        exception,
+                        failureNow
+                );
+                return buildResult(failureJob, persistenceResult.getRollbackChanges(), false, controlMode);
+            }
+        } finally {
+            redisUtil.compareAndDelete(lockKey, lockToken);
         }
-        if (refreshedConfigCache) {
-            configService.refreshSystemConfigCache();
-        }
-        aiAutoTuneChangeService.saveBatch(rollbackChanges);
-        finishRollbackJob(rollbackJob, rollbackChanges, now);
-        aiAutoTuneJobService.updateById(rollbackJob);
-        return buildResult(rollbackJob, rollbackChanges, false);
+    }
+
+    private AutoTuneApplyResult rejectRollbackForUnavailableLock(String reason,
+                                                                 Date now,
+                                                                 String lockKey,
+                                                                 AutoTuneControlModeSnapshot controlMode) {
+        boolean lockKeyExists = redisUtil.hasKey(lockKey);
+        LOGGER.warn("鐢宠AI鑷姩璋冨弬 rollback 閿佸け璐ワ紝lockKey={}, lockKeyExists={}", lockKey, lockKeyExists);
+        AiAutoTuneJob rollbackJob = createRollbackJob(reason, now);
+        RollbackPersistenceResult persistenceResult = persistRollbackLockFailureInTransaction(
+                rollbackJob,
+                now
+        );
+        return buildResult(rollbackJob, persistenceResult.getRollbackChanges(), false, controlMode);
+    }
+
+    private AutoTuneApplyResult rejectRollbackForAnalysisOnly(AiAutoTuneJob rollbackJob,
+                                                              Date now,
+                                                              AutoTuneControlModeSnapshot controlMode) {
+        RollbackPersistenceResult persistenceResult = persistAnalysisOnlyRollbackRejectionInTransaction(
+                rollbackJob,
+                now
+        );
+        AutoTuneApplyResult result = buildResult(rollbackJob, persistenceResult.getRollbackChanges(), false, controlMode);
+        result.setAnalysisOnly(true);
+        result.setNoApply(true);
+        return result;
     }
 
     private List<ValidatedChange> validateChanges(AutoTuneApplyRequest request, boolean dryRun, Date now) {
@@ -145,31 +314,37 @@
 
     private ValidatedChange validateChange(AutoTuneChangeCommand command, boolean dryRun, Date now) {
         ValidatedChange validatedChange = new ValidatedChange(command);
-        AutoTuneRuleDefinition.Rule rule = AutoTuneRuleDefinition.findRule(command.getTargetType(), command.getTargetKey());
+        AutoTuneRuleDefinition.Rule rule = AutoTuneRuleDefinition.findRule(
+                validatedChange.getTargetType(),
+                validatedChange.getTargetKey()
+        );
         if (rule == null) {
-            return validatedChange.reject("涓嶆敮鎸佺殑璋冨弬鐩爣: " + command.getTargetType() + "/" + command.getTargetKey());
+            return validatedChange.reject("涓嶆敮鎸佺殑璋冨弬鐩爣: "
+                    + validatedChange.getTargetType() + "/" + validatedChange.getTargetKey());
         }
         validatedChange.setRule(rule);
 
-        Integer requestedValue = parseRequestedInt(command.getNewValue());
+        Integer requestedValue = parseRequestedInt(validatedChange.getRawRequestedValue());
         if (requestedValue == null) {
-            return validatedChange.reject(command.getTargetKey() + " 蹇呴』涓烘暣鏁�");
+            return validatedChange.reject(validatedChange.getTargetKey() + " 蹇呴』涓烘暣鏁�");
         }
         validatedChange.setRequestedIntValue(requestedValue);
         validatedChange.setRequestedValue(String.valueOf(requestedValue));
 
-        CurrentValue currentValue = readCurrentValueForValidation(command, rule);
+        CurrentValue currentValue = readCurrentValueForValidation(validatedChange, rule);
         if (currentValue.getRejectReason() != null) {
             return validatedChange.reject(currentValue.getRejectReason());
         }
         validatedChange.setOldValue(currentValue.getOldValue());
 
-        Integer maxValue = resolveMaxValue(command, rule, requestedValue);
+        Integer maxValue = resolveMaxValue(validatedChange, rule, requestedValue);
         if (maxValue == null) {
-            return validatedChange.reject("绔欑偣 " + command.getTargetId() + " 缂哄皯 OUT 鏂瑰悜 bufferCapacity锛屾棤娉曡瘉鏄� outTaskLimit 涓婇檺");
+            return validatedChange.reject("绔欑偣 " + validatedChange.getTargetId()
+                    + " 缂哄皯 outBufferCapacity锛屾棤娉曡瘉鏄� outTaskLimit 涓婇檺");
         }
         if (requestedValue < rule.getMinValue() || requestedValue > maxValue) {
-            return validatedChange.reject(command.getTargetKey() + " 蹇呴』鍦� " + rule.getMinValue() + "~" + maxValue + " 鑼冨洿鍐�");
+            return validatedChange.reject(validatedChange.getTargetKey() + " 蹇呴』鍦� "
+                    + rule.getMinValue() + "~" + maxValue + " 鑼冨洿鍐�");
         }
 
         if (Objects.equals(currentValue.getNumericValue(), requestedValue)) {
@@ -177,10 +352,10 @@
         }
         int step = Math.abs(requestedValue - currentValue.getNumericValue());
         if (step > rule.getMaxStep()) {
-            return validatedChange.reject(command.getTargetKey() + " 鍗曟璋冩暣姝ラ暱涓嶈兘瓒呰繃 " + rule.getMaxStep());
+            return validatedChange.reject(validatedChange.getTargetKey() + " 鍗曟璋冩暣姝ラ暱涓嶈兘瓒呰繃 " + rule.getMaxStep());
         }
 
-        Date cooldownExpireTime = findCooldownExpireTime(command, now);
+        Date cooldownExpireTime = findCooldownExpireTime(validatedChange, now);
         if (cooldownExpireTime != null) {
             validatedChange.setCooldownExpireTime(cooldownExpireTime);
             return validatedChange.reject("鐩爣浠嶅湪鍐峰嵈鏈燂紝鍐峰嵈鎴鏃堕棿: " + cooldownExpireTime);
@@ -191,86 +366,81 @@
         return validatedChange.accept(dryRun ? ChangeStatus.DRY_RUN : ChangeStatus.PENDING, null);
     }
 
-    private CurrentValue readCurrentValueForValidation(AutoTuneChangeCommand command, AutoTuneRuleDefinition.Rule rule) {
+    private CurrentValue readCurrentValueForValidation(ValidatedChange validatedChange, AutoTuneRuleDefinition.Rule rule) {
         AutoTuneTargetType targetType = rule.getTargetType();
-        Integer targetId = parseTargetId(command.getTargetId(), targetType);
+        Integer targetId = parseTargetId(validatedChange.getTargetId(), targetType);
         if (!AutoTuneTargetType.SYS_CONFIG.equals(targetType) && targetId == null) {
             return CurrentValue.rejected("targetId 蹇呴』涓烘暣鏁�");
         }
         if (AutoTuneTargetType.SYS_CONFIG.equals(targetType)) {
-            Config config = configService.getOne(new QueryWrapper<Config>().eq("code", command.getTargetKey()).last("limit 1"));
+            Config config = configService.getOne(new QueryWrapper<Config>().eq("code", validatedChange.getTargetKey()).last("limit 1"));
             if (config == null) {
-                return CurrentValue.rejected("杩愯鍙傛暟涓嶅瓨鍦�: " + command.getTargetKey());
+                return CurrentValue.rejected("杩愯鍙傛暟涓嶅瓨鍦�: " + validatedChange.getTargetKey());
             }
-            return numericCurrentValue(config.getValue(), false, command.getTargetKey());
+            return numericCurrentValue(config.getValue(), validatedChange.getTargetKey());
         }
         if (AutoTuneTargetType.STATION.equals(targetType)) {
             BasStation station = basStationService.getById(targetId);
             if (station == null) {
-                return CurrentValue.rejected("绔欑偣涓嶅瓨鍦�: " + command.getTargetId());
+                return CurrentValue.rejected("绔欑偣涓嶅瓨鍦�: " + validatedChange.getTargetId());
             }
-            return numericCurrentValue(toText(station.getOutTaskLimit()), true, command.getTargetKey());
+            return stationOutTaskLimitCurrentValue(station.getOutTaskLimit(), validatedChange.getTargetKey());
         }
         if (AutoTuneTargetType.CRN.equals(targetType)) {
             BasCrnp crnp = basCrnpService.getById(targetId);
             if (crnp == null) {
-                return CurrentValue.rejected("鍫嗗灈鏈轰笉瀛樺湪: " + command.getTargetId());
+                return CurrentValue.rejected("鍫嗗灈鏈轰笉瀛樺湪: " + validatedChange.getTargetId());
             }
-            Integer value = "maxOutTask".equals(command.getTargetKey()) ? crnp.getMaxOutTask() : crnp.getMaxInTask();
-            return numericCurrentValue(toText(value), false, command.getTargetKey());
+            Integer value = "maxOutTask".equals(validatedChange.getTargetKey()) ? crnp.getMaxOutTask() : crnp.getMaxInTask();
+            return numericCurrentValue(toText(value), validatedChange.getTargetKey());
         }
         BasDualCrnp dualCrnp = basDualCrnpService.getById(targetId);
         if (dualCrnp == null) {
-            return CurrentValue.rejected("鍙屽伐浣嶅爢鍨涙満涓嶅瓨鍦�: " + command.getTargetId());
+            return CurrentValue.rejected("鍙屽伐浣嶅爢鍨涙満涓嶅瓨鍦�: " + validatedChange.getTargetId());
         }
-        Integer value = "maxOutTask".equals(command.getTargetKey()) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask();
-        return numericCurrentValue(toText(value), false, command.getTargetKey());
+        Integer value = "maxOutTask".equals(validatedChange.getTargetKey()) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask();
+        return numericCurrentValue(toText(value), validatedChange.getTargetKey());
     }
 
-    private CurrentValue numericCurrentValue(String oldValue, boolean nullOrNegativeAsZero, String targetKey) {
+    private CurrentValue stationOutTaskLimitCurrentValue(Integer outTaskLimit, String targetKey) {
+        if (outTaskLimit == null || outTaskLimit < 0) {
+            return CurrentValue.rejected(targetKey + " 褰撳墠涓轰笉闄愬埗锛岄渶瑕佷汉宸ュ厛鍒濆鍖栦负鏈夐檺鍊煎悗鎵嶈兘鑷姩璋冨弬");
+        }
+        return numericCurrentValue(toText(outTaskLimit), targetKey);
+    }
+
+    private CurrentValue numericCurrentValue(String oldValue, String targetKey) {
         if (oldValue == null || oldValue.trim().isEmpty()) {
-            if (nullOrNegativeAsZero) {
-                return CurrentValue.accepted(null, 0);
-            }
             return CurrentValue.rejected(targetKey + " 褰撳墠鍊间负绌猴紝鏃犳硶璁$畻姝ラ暱");
         }
         try {
             Integer parsedValue = Integer.valueOf(oldValue.trim());
-            if (nullOrNegativeAsZero && parsedValue < 0) {
-                return CurrentValue.accepted(oldValue, 0);
-            }
             return CurrentValue.accepted(oldValue, parsedValue);
         } catch (Exception exception) {
             return CurrentValue.rejected(targetKey + " 褰撳墠鍊间笉鏄暣鏁帮紝鏃犳硶璁$畻姝ラ暱");
         }
     }
 
-    private Integer resolveMaxValue(AutoTuneChangeCommand command,
+    private Integer resolveMaxValue(ValidatedChange validatedChange,
                                     AutoTuneRuleDefinition.Rule rule,
                                     Integer requestedValue) {
         if (!rule.isDynamicMaxValue()) {
             return rule.getMaxValue();
         }
-        Integer targetId = parseTargetId(command.getTargetId(), rule.getTargetType());
-        StationFlowCapacity capacity = stationFlowCapacityService.getOne(
-                new QueryWrapper<StationFlowCapacity>()
-                        .eq("station_id", targetId)
-                        .eq("direction_code", DIRECTION_OUT)
-                        .last("limit 1")
-        );
-        if (capacity == null || capacity.getBufferCapacity() == null) {
+        Integer targetId = parseTargetId(validatedChange.getTargetId(), rule.getTargetType());
+        BasStation station = basStationService.getById(targetId);
+        if (station == null || station.getOutBufferCapacity() == null) {
             return requestedValue == 0 ? 0 : null;
         }
-        return Math.max(0, capacity.getBufferCapacity());
+        return Math.max(0, station.getOutBufferCapacity());
     }
 
-    private Date findCooldownExpireTime(AutoTuneChangeCommand command, Date now) {
-        String targetId = normalizeTargetId(command.getTargetType(), command.getTargetId());
+    private Date findCooldownExpireTime(ValidatedChange validatedChange, Date now) {
         List<AiAutoTuneChange> recentChanges = aiAutoTuneChangeService.list(
                 new QueryWrapper<AiAutoTuneChange>()
-                        .eq("target_type", command.getTargetType())
-                        .eq("target_id", targetId)
-                        .eq("target_key", command.getTargetKey())
+                        .eq("target_type", validatedChange.getTargetType())
+                        .eq("target_id", validatedChange.getTargetId())
+                        .eq("target_key", validatedChange.getTargetKey())
                         .eq("result_status", ChangeStatus.SUCCESS.getCode())
                         .gt("cooldown_expire_time", now)
                         .orderByDesc("create_time")
@@ -287,68 +457,331 @@
         return cooldownExpireTime;
     }
 
-    private void applyValidatedChanges(List<ValidatedChange> validatedChanges) {
+    private ApplyPersistenceResult persistApplyResultInTransaction(AiAutoTuneJob job,
+                                                                   AutoTuneApplyRequest request,
+                                                                   List<ValidatedChange> validatedChanges,
+                                                                   boolean dryRun,
+                                                                   Date now,
+                                                                   boolean writeTargets) {
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        return transactionTemplate.execute(status -> persistApplyResult(
+                job,
+                request,
+                validatedChanges,
+                dryRun,
+                now,
+                writeTargets
+        ));
+    }
+
+    private ApplyPersistenceResult persistApplyResult(AiAutoTuneJob job,
+                                                     AutoTuneApplyRequest request,
+                                                     List<ValidatedChange> validatedChanges,
+                                                     boolean dryRun,
+                                                     Date now,
+                                                     boolean writeTargets) {
+        saveJob(job);
+        boolean refreshConfigCache = false;
+        if (writeTargets) {
+            refreshConfigCache = applyValidatedChanges(validatedChanges);
+        }
+        List<AiAutoTuneChange> auditChanges = buildAuditChanges(job.getId(), validatedChanges, now);
+        saveAuditChanges(auditChanges);
+        finishJob(job, request, auditChanges, dryRun, now);
+        updateJob(job);
+        return new ApplyPersistenceResult(auditChanges, refreshConfigCache);
+    }
+
+    private void saveJob(AiAutoTuneJob job) {
+        if (!aiAutoTuneJobService.save(job)) {
+            throw new IllegalStateException("淇濆瓨璋冨弬浠诲姟澶辫触");
+        }
+    }
+
+    private boolean applyValidatedChanges(List<ValidatedChange> validatedChanges) {
         boolean refreshConfigCache = false;
         for (ValidatedChange validatedChange : validatedChanges) {
             if (ChangeStatus.NO_CHANGE.equals(validatedChange.getStatus())) {
                 continue;
             }
-            AutoTuneChangeCommand command = validatedChange.getCommand();
-            writeValue(command.getTargetType(), command.getTargetId(), command.getTargetKey(), validatedChange.getRequestedValue());
+            writeValue(
+                    validatedChange.getTargetType(),
+                    validatedChange.getTargetId(),
+                    validatedChange.getTargetKey(),
+                    validatedChange.getRequestedValue()
+            );
             validatedChange.accept(ChangeStatus.SUCCESS, null);
             validatedChange.setAppliedValue(validatedChange.getRequestedValue());
-            if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(command.getTargetType())) {
+            if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(validatedChange.getTargetType())) {
                 refreshConfigCache = true;
             }
         }
-        if (refreshConfigCache) {
+        return refreshConfigCache;
+    }
+
+    private void saveAuditChanges(List<AiAutoTuneChange> auditChanges) {
+        if (auditChanges.isEmpty()) {
+            return;
+        }
+        if (!aiAutoTuneChangeService.saveBatch(auditChanges)) {
+            throw new IllegalStateException("淇濆瓨璋冨弬瀹¤澶辫触");
+        }
+    }
+
+    private void updateJob(AiAutoTuneJob job) {
+        if (!aiAutoTuneJobService.updateById(job)) {
+            throw new IllegalStateException("鏇存柊璋冨弬浠诲姟鐘舵�佸け璐�");
+        }
+    }
+
+    private void refreshSystemConfigCacheSafely(ApplyPersistenceResult persistenceResult) {
+        if (persistenceResult != null && persistenceResult.isRefreshConfigCache()) {
+            refreshSystemConfigCacheSafely("apply");
+        }
+    }
+
+    private void persistNoRollbackSourceJobInTransaction(AiAutoTuneJob rollbackJob, Date now) {
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        transactionTemplate.executeWithoutResult(status -> {
+            saveJob(rollbackJob);
+            rollbackJob.setStatus(AutoTuneJobStatus.REJECTED.getCode());
+            rollbackJob.setFinishTime(now);
+            rollbackJob.setRejectCount(0);
+            rollbackJob.setSuccessCount(0);
+            rollbackJob.setSummary("鏈壘鍒板彲鍥炴粴鐨勬垚鍔熻皟鍙傝褰�");
+            rollbackJob.setErrorMessage("鏈壘鍒板彲鍥炴粴鐨勬垚鍔熻皟鍙傝褰�");
+            updateJob(rollbackJob);
+        });
+    }
+
+    private RollbackPersistenceResult persistRollbackResultInTransaction(AiAutoTuneJob rollbackJob,
+                                                                        List<AiAutoTuneChange> sourceChanges,
+                                                                        Date now) {
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        return transactionTemplate.execute(status -> {
+            saveJob(rollbackJob);
+            RollbackPersistenceResult rollbackResult = rollbackChanges(rollbackJob.getId(), sourceChanges, now);
+            saveAuditChanges(rollbackResult.getRollbackChanges());
+            finishRollbackJob(rollbackJob, rollbackResult.getRollbackChanges(), now);
+            updateJob(rollbackJob);
+            return rollbackResult;
+        });
+    }
+
+    private RollbackPersistenceResult persistFailedRollbackResultInTransaction(AiAutoTuneJob rollbackJob,
+                                                                              List<AiAutoTuneChange> sourceChanges,
+                                                                              RuntimeException exception,
+                                                                              Date now) {
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        return transactionTemplate.execute(status -> {
+            saveJob(rollbackJob);
+            List<AiAutoTuneChange> rollbackChanges = buildFailedRollbackChanges(
+                    rollbackJob.getId(),
+                    sourceChanges,
+                    exception,
+                    now
+            );
+            saveAuditChanges(rollbackChanges);
+            finishRollbackJob(rollbackJob, rollbackChanges, now);
+            updateJob(rollbackJob);
+            return new RollbackPersistenceResult(rollbackChanges, false);
+        });
+    }
+
+    private RollbackPersistenceResult persistRollbackLockFailureInTransaction(AiAutoTuneJob rollbackJob, Date now) {
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        return transactionTemplate.execute(status -> {
+            saveJob(rollbackJob);
+            List<AiAutoTuneChange> rollbackChanges = buildRollbackLockFailureChanges(rollbackJob.getId(), now);
+            saveAuditChanges(rollbackChanges);
+            finishRollbackJob(rollbackJob, rollbackChanges, now);
+            updateJob(rollbackJob);
+            return new RollbackPersistenceResult(rollbackChanges, false);
+        });
+    }
+
+    private ApplyPersistenceResult persistAnalysisOnlyApplyRejectionInTransaction(AiAutoTuneJob job,
+                                                                                  AutoTuneApplyRequest request,
+                                                                                  Date now) {
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        return transactionTemplate.execute(status -> {
+            saveJob(job);
+            List<AiAutoTuneChange> auditChanges = buildAnalysisOnlyApplyChanges(job.getId(), request, now);
+            saveAuditChanges(auditChanges);
+            finishAnalysisOnlyRejectedJob(job, auditChanges, now);
+            updateJob(job);
+            return new ApplyPersistenceResult(auditChanges, false);
+        });
+    }
+
+    private RollbackPersistenceResult persistAnalysisOnlyRollbackRejectionInTransaction(AiAutoTuneJob rollbackJob,
+                                                                                       Date now) {
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        return transactionTemplate.execute(status -> {
+            saveJob(rollbackJob);
+            List<AiAutoTuneChange> rollbackChanges = buildAnalysisOnlyRollbackChanges(rollbackJob.getId(), now);
+            saveAuditChanges(rollbackChanges);
+            finishAnalysisOnlyRejectedJob(rollbackJob, rollbackChanges, now);
+            updateJob(rollbackJob);
+            return new RollbackPersistenceResult(rollbackChanges, false);
+        });
+    }
+
+    private RollbackPersistenceResult rollbackChanges(Long rollbackJobId, List<AiAutoTuneChange> sourceChanges, Date now) {
+        List<AiAutoTuneChange> rollbackChanges = new ArrayList<>();
+        boolean refreshConfigCache = false;
+        for (AiAutoTuneChange sourceChange : sourceChanges) {
+            AiAutoTuneChange rollbackChange = buildRollbackChange(rollbackJobId, sourceChange, now);
+            String currentValue = readCurrentValue(
+                    sourceChange.getTargetType(),
+                    sourceChange.getTargetId(),
+                    sourceChange.getTargetKey()
+            );
+            rollbackChange.setOldValue(currentValue);
+            writeValue(sourceChange.getTargetType(), sourceChange.getTargetId(), sourceChange.getTargetKey(), sourceChange.getOldValue());
+            if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(sourceChange.getTargetType())) {
+                refreshConfigCache = true;
+            }
+            rollbackChange.setAppliedValue(sourceChange.getOldValue());
+            rollbackChange.setResultStatus(ChangeStatus.SUCCESS.getCode());
+            rollbackChanges.add(rollbackChange);
+        }
+        return new RollbackPersistenceResult(rollbackChanges, refreshConfigCache);
+    }
+
+    private List<AiAutoTuneChange> buildRollbackLockFailureChanges(Long rollbackJobId, Date now) {
+        AiAutoTuneChange rollbackChange = new AiAutoTuneChange();
+        rollbackChange.setJobId(rollbackJobId);
+        rollbackChange.setTargetType(AutoTuneTriggerType.ROLLBACK.getCode());
+        rollbackChange.setTargetId("");
+        rollbackChange.setTargetKey("latest_successful_job");
+        rollbackChange.setResultStatus(ChangeStatus.FAILED.getCode());
+        rollbackChange.setRejectReason(APPLY_LOCK_BUSY_REASON);
+        rollbackChange.setCreateTime(now);
+        return List.of(rollbackChange);
+    }
+
+    private List<AiAutoTuneChange> buildAnalysisOnlyApplyChanges(Long jobId,
+                                                                 AutoTuneApplyRequest request,
+                                                                 Date now) {
+        List<AiAutoTuneChange> changes = new ArrayList<>();
+        List<AutoTuneChangeCommand> commands = request == null ? null : request.getChanges();
+        if (commands == null || commands.isEmpty()) {
+            changes.add(buildAnalysisOnlyChange(jobId, "analysis_only", "", "apply", null, now));
+            return changes;
+        }
+        for (AutoTuneChangeCommand command : commands) {
+            changes.add(buildAnalysisOnlyChange(
+                    jobId,
+                    normalizeText(command == null ? null : command.getTargetType()),
+                    normalizeText(command == null ? null : command.getTargetId()),
+                    normalizeText(command == null ? null : command.getTargetKey()),
+                    command == null ? null : command.getNewValue(),
+                    now
+            ));
+        }
+        return changes;
+    }
+
+    private List<AiAutoTuneChange> buildAnalysisOnlyRollbackChanges(Long jobId, Date now) {
+        AiAutoTuneChange rollbackChange = buildAnalysisOnlyChange(
+                jobId,
+                "analysis_only",
+                "",
+                "rollback",
+                null,
+                now
+        );
+        return List.of(rollbackChange);
+    }
+
+    private AiAutoTuneChange buildAnalysisOnlyChange(Long jobId,
+                                                     String targetType,
+                                                     String targetId,
+                                                     String targetKey,
+                                                     String requestedValue,
+                                                     Date now) {
+        AiAutoTuneChange change = new AiAutoTuneChange();
+        change.setJobId(jobId);
+        change.setTargetType(targetType);
+        change.setTargetId(targetId);
+        change.setTargetKey(targetKey);
+        change.setRequestedValue(requestedValue);
+        change.setResultStatus(ChangeStatus.REJECTED.getCode());
+        change.setRejectReason(ANALYSIS_ONLY_REJECT_REASON);
+        change.setCreateTime(now);
+        return change;
+    }
+
+    private void refreshRollbackConfigCacheSafely(RollbackPersistenceResult persistenceResult) {
+        if (persistenceResult != null && persistenceResult.isRefreshConfigCache()) {
+            refreshSystemConfigCacheSafely("rollback");
+        }
+    }
+
+    private void refreshSystemConfigCacheSafely(String scene) {
+        try {
             configService.refreshSystemConfigCache();
+        } catch (RuntimeException exception) {
+            LOGGER.warn("AI鑷姩璋冨弬{}宸叉彁浜わ紝浣嗗埛鏂扮郴缁熼厤缃紦瀛樺け璐�", scene, exception);
         }
     }
 
     private void writeValue(String targetType, String targetId, String targetKey, String value) {
-        AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType);
+        String normalizedTargetType = normalizeText(targetType);
+        String normalizedTargetKey = normalizeText(targetKey);
+        String normalizedTargetId = normalizeTargetId(normalizedTargetType, targetId);
+        AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(normalizedTargetType);
+        if (parsedTargetType == null) {
+            throw new IllegalArgumentException("涓嶆敮鎸佺殑璋冨弬鐩爣: " + normalizedTargetType + "/" + normalizedTargetKey);
+        }
         if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) {
-            if (!configService.saveConfigValue(targetKey, value)) {
-                throw new IllegalStateException("淇濆瓨杩愯鍙傛暟澶辫触: " + targetKey);
+            if (!configService.saveConfigValue(normalizedTargetKey, value)) {
+                throw new IllegalStateException("淇濆瓨杩愯鍙傛暟澶辫触: " + normalizedTargetKey);
             }
             return;
         }
-        Integer parsedTargetId = parseTargetId(targetId, parsedTargetType);
+        Integer parsedTargetId = parseTargetId(normalizedTargetId, parsedTargetType);
         Integer intValue = value == null ? null : Integer.valueOf(value);
         if (AutoTuneTargetType.STATION.equals(parsedTargetType)) {
             boolean updated = basStationService.update(new UpdateWrapper<BasStation>()
                     .eq("station_id", parsedTargetId)
                     .set("out_task_limit", intValue));
             if (!updated) {
-                throw new IllegalStateException("淇濆瓨绔欑偣鍙傛暟澶辫触: " + targetId + "/" + targetKey);
+                throw new IllegalStateException("淇濆瓨绔欑偣鍙傛暟澶辫触: " + normalizedTargetId + "/" + normalizedTargetKey);
             }
             return;
         }
         if (AutoTuneTargetType.CRN.equals(parsedTargetType)) {
-            String column = "maxOutTask".equals(targetKey) ? "max_out_task" : "max_in_task";
+            String column = "maxOutTask".equals(normalizedTargetKey) ? "max_out_task" : "max_in_task";
             boolean updated = basCrnpService.update(new UpdateWrapper<BasCrnp>()
                     .eq("crn_no", parsedTargetId)
                     .set(column, intValue));
             if (!updated) {
-                throw new IllegalStateException("淇濆瓨鍫嗗灈鏈哄弬鏁板け璐�: " + targetId + "/" + targetKey);
+                throw new IllegalStateException("淇濆瓨鍫嗗灈鏈哄弬鏁板け璐�: " + normalizedTargetId + "/" + normalizedTargetKey);
             }
             return;
         }
-        String column = "maxOutTask".equals(targetKey) ? "max_out_task" : "max_in_task";
+        String column = "maxOutTask".equals(normalizedTargetKey) ? "max_out_task" : "max_in_task";
         boolean updated = basDualCrnpService.update(new UpdateWrapper<BasDualCrnp>()
                 .eq("crn_no", parsedTargetId)
                 .set(column, intValue));
         if (!updated) {
-            throw new IllegalStateException("淇濆瓨鍙屽伐浣嶅爢鍨涙満鍙傛暟澶辫触: " + targetId + "/" + targetKey);
+            throw new IllegalStateException("淇濆瓨鍙屽伐浣嶅爢鍨涙満鍙傛暟澶辫触: " + normalizedTargetId + "/" + normalizedTargetKey);
         }
     }
 
     private String readCurrentValue(String targetType, String targetId, String targetKey) {
-        AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType);
-        Integer parsedTargetId = parseTargetId(targetId, parsedTargetType);
+        String normalizedTargetType = normalizeText(targetType);
+        String normalizedTargetKey = normalizeText(targetKey);
+        String normalizedTargetId = normalizeTargetId(normalizedTargetType, targetId);
+        AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(normalizedTargetType);
+        if (parsedTargetType == null) {
+            throw new IllegalArgumentException("涓嶆敮鎸佺殑璋冨弬鐩爣: " + normalizedTargetType + "/" + normalizedTargetKey);
+        }
+        Integer parsedTargetId = parseTargetId(normalizedTargetId, parsedTargetType);
         if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) {
-            Config config = configService.getOne(new QueryWrapper<Config>().eq("code", targetKey).last("limit 1"));
+            Config config = configService.getOne(new QueryWrapper<Config>().eq("code", normalizedTargetKey).last("limit 1"));
             return config == null ? null : config.getValue();
         }
         if (AutoTuneTargetType.STATION.equals(parsedTargetType)) {
@@ -360,13 +793,13 @@
             if (crnp == null) {
                 return null;
             }
-            return toText("maxOutTask".equals(targetKey) ? crnp.getMaxOutTask() : crnp.getMaxInTask());
+            return toText("maxOutTask".equals(normalizedTargetKey) ? crnp.getMaxOutTask() : crnp.getMaxInTask());
         }
         BasDualCrnp dualCrnp = basDualCrnpService.getById(parsedTargetId);
         if (dualCrnp == null) {
             return null;
         }
-        return toText("maxOutTask".equals(targetKey) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask());
+        return toText("maxOutTask".equals(normalizedTargetKey) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask());
     }
 
     private AiAutoTuneJob createJob(AutoTuneApplyRequest request, boolean dryRun, Date now) {
@@ -374,7 +807,7 @@
         job.setTriggerType(AutoTuneTriggerType.normalize(request.getTriggerType()));
         job.setStatus(AutoTuneJobStatus.RUNNING.getCode());
         job.setStartTime(now);
-        job.setHasActiveTasks(0);
+        job.setHasActiveTasks(resolveHasActiveTasksForAudit());
         job.setPromptSceneCode(PROMPT_SCENE_CODE);
         job.setSummary(dryRun ? "AI鑷姩璋冨弬 dry-run: " + safeReason(request.getReason()) : safeReason(request.getReason()));
         job.setIntervalBefore(readIntervalMinutes());
@@ -393,7 +826,7 @@
         job.setTriggerType(AutoTuneTriggerType.ROLLBACK.getCode());
         job.setStatus(AutoTuneJobStatus.RUNNING.getCode());
         job.setStartTime(now);
-        job.setHasActiveTasks(0);
+        job.setHasActiveTasks(resolveHasActiveTasksForAudit());
         job.setPromptSceneCode(PROMPT_SCENE_CODE);
         job.setSummary(safeReason(reason));
         job.setIntervalBefore(readIntervalMinutes());
@@ -405,6 +838,21 @@
         job.setTotalTokens(0);
         job.setCreateTime(now);
         return job;
+    }
+
+    private int resolveHasActiveTasksForAudit() {
+        if (wrkMastService == null) {
+            LOGGER.warn("AI鑷姩璋冨弬瀹¤鏃犳硶鑾峰彇 WrkMastService锛宧asActiveTasks 鎸� 0 璁板綍");
+            return 0;
+        }
+        try {
+            QueryWrapper<WrkMast> queryWrapper = new QueryWrapper<>();
+            queryWrapper.and(wrapper -> wrapper.notIn("wrk_sts", FINAL_WRK_STS_LIST).or().isNull("wrk_sts"));
+            return wrkMastService.count(queryWrapper) > 0 ? 1 : 0;
+        } catch (RuntimeException exception) {
+            LOGGER.warn("AI鑷姩璋冨弬瀹¤鏌ヨ鏈畬鎴愪换鍔″け璐ワ紝hasActiveTasks 鎸� 0 璁板綍", exception);
+            return 0;
+        }
     }
 
     private void finishJob(AiAutoTuneJob job,
@@ -425,6 +873,17 @@
         }
     }
 
+    private void finishAnalysisOnlyRejectedJob(AiAutoTuneJob job, List<AiAutoTuneChange> changes, Date now) {
+        int rejectCount = Math.max(1, countRejected(changes));
+        job.setFinishTime(now);
+        job.setSuccessCount(0);
+        job.setRejectCount(rejectCount);
+        job.setIntervalAfter(readIntervalMinutes());
+        job.setStatus(AutoTuneJobStatus.REJECTED.getCode());
+        job.setSummary(ANALYSIS_ONLY_REJECT_REASON);
+        job.setErrorMessage(ANALYSIS_ONLY_REJECT_REASON);
+    }
+
     private void finishRollbackJob(AiAutoTuneJob job, List<AiAutoTuneChange> changes, Date now) {
         int successCount = countAccepted(changes);
         int rejectCount = countRejected(changes);
@@ -432,7 +891,15 @@
         job.setSuccessCount(successCount);
         job.setRejectCount(rejectCount);
         job.setIntervalAfter(readIntervalMinutes());
-        job.setStatus(rejectCount == 0 ? AutoTuneJobStatus.SUCCESS.getCode() : AutoTuneJobStatus.PARTIAL_SUCCESS.getCode());
+        if (rejectCount == 0) {
+            job.setStatus(AutoTuneJobStatus.SUCCESS.getCode());
+        } else if (successCount == 0 && hasFailedChange(changes)) {
+            job.setStatus(AutoTuneJobStatus.FAILED.getCode());
+        } else if (successCount == 0) {
+            job.setStatus(AutoTuneJobStatus.REJECTED.getCode());
+        } else {
+            job.setStatus(AutoTuneJobStatus.PARTIAL_SUCCESS.getCode());
+        }
         job.setSummary("鍥炴粴鏈�杩戜竴娆℃垚鍔熻皟鍙傦紝鎴愬姛 " + successCount + " 椤癸紝澶辫触 " + rejectCount + " 椤�");
         if (rejectCount > 0) {
             job.setErrorMessage(firstRejectReason(changes));
@@ -442,6 +909,9 @@
     private String resolveJobStatus(List<AiAutoTuneChange> auditChanges, boolean dryRun) {
         if (auditChanges.isEmpty()) {
             return AutoTuneJobStatus.NO_CHANGE.getCode();
+        }
+        if (hasFailedChange(auditChanges)) {
+            return AutoTuneJobStatus.FAILED.getCode();
         }
         int rejectedCount = countRejected(auditChanges);
         int acceptedCount = countAccepted(auditChanges);
@@ -468,13 +938,14 @@
         List<AiAutoTuneChange> auditChanges = new ArrayList<>();
         for (ValidatedChange validatedChange : validatedChanges) {
             AiAutoTuneChange change = new AiAutoTuneChange();
-            AutoTuneChangeCommand command = validatedChange.getCommand();
             change.setJobId(jobId);
-            change.setTargetType(command.getTargetType());
-            change.setTargetId(normalizeTargetId(command.getTargetType(), command.getTargetId()));
-            change.setTargetKey(command.getTargetKey());
+            change.setTargetType(validatedChange.getTargetType());
+            change.setTargetId(validatedChange.getTargetId());
+            change.setTargetKey(validatedChange.getTargetKey());
             change.setOldValue(validatedChange.getOldValue());
-            change.setRequestedValue(validatedChange.getRequestedValue() == null ? command.getNewValue() : validatedChange.getRequestedValue());
+            change.setRequestedValue(validatedChange.getRequestedValue() == null
+                    ? validatedChange.getRawRequestedValue()
+                    : validatedChange.getRequestedValue());
             change.setAppliedValue(validatedChange.getAppliedValue());
             change.setResultStatus(validatedChange.getStatus().getCode());
             change.setRejectReason(validatedChange.getRejectReason());
@@ -497,17 +968,26 @@
     }
 
     private List<AiAutoTuneChange> findLatestSuccessfulChanges() {
-        List<AiAutoTuneJob> jobs = aiAutoTuneJobService.list(
-                new QueryWrapper<AiAutoTuneJob>()
-                        .eq("status", AutoTuneJobStatus.SUCCESS.getCode())
-                        .ne("trigger_type", AutoTuneTriggerType.ROLLBACK.getCode())
-                        .orderByDesc("finish_time")
-                        .last("limit 20")
+        List<AiAutoTuneChange> successfulChanges = aiAutoTuneChangeService.list(
+                new QueryWrapper<AiAutoTuneChange>()
+                        .eq("result_status", ChangeStatus.SUCCESS.getCode())
+                        .orderByDesc("create_time")
+                        .orderByDesc("id")
         );
-        if (jobs == null || jobs.isEmpty()) {
+        if (successfulChanges == null || successfulChanges.isEmpty()) {
             return new ArrayList<>();
         }
-        for (AiAutoTuneJob job : jobs) {
+        Set<Long> checkedJobIds = new HashSet<>();
+        for (AiAutoTuneChange successfulChange : successfulChanges) {
+            Long jobId = successfulChange.getJobId();
+            if (jobId == null || checkedJobIds.contains(jobId)) {
+                continue;
+            }
+            checkedJobIds.add(jobId);
+            AiAutoTuneJob job = aiAutoTuneJobService.getById(jobId);
+            if (!isRollbackCandidate(job)) {
+                continue;
+            }
             List<AiAutoTuneChange> changes = aiAutoTuneChangeService.list(
                     new QueryWrapper<AiAutoTuneChange>()
                             .eq("job_id", job.getId())
@@ -521,11 +1001,26 @@
         return new ArrayList<>();
     }
 
-    private AutoTuneApplyResult buildResult(AiAutoTuneJob job, List<AiAutoTuneChange> changes, boolean dryRun) {
+    private boolean isRollbackCandidate(AiAutoTuneJob job) {
+        if (job == null) {
+            return false;
+        }
+        if (!AutoTuneJobStatus.SUCCESS.getCode().equals(job.getStatus())) {
+            return false;
+        }
+        return !AutoTuneTriggerType.ROLLBACK.getCode().equals(job.getTriggerType());
+    }
+
+    private AutoTuneApplyResult buildResult(AiAutoTuneJob job,
+                                            List<AiAutoTuneChange> changes,
+                                            boolean dryRun,
+                                            AutoTuneControlModeSnapshot controlMode) {
         AutoTuneApplyResult result = new AutoTuneApplyResult();
         result.setDryRun(dryRun);
         result.setSuccess(AutoTuneJobStatus.SUCCESS.getCode().equals(job.getStatus())
                 || AutoTuneJobStatus.NO_CHANGE.getCode().equals(job.getStatus()));
+        result.setAnalysisOnly(isAnalysisOnly(controlMode));
+        result.setNoApply(false);
         result.setJobId(job.getId());
         result.setSummary(job.getSummary());
         result.setSuccessCount(job.getSuccessCount());
@@ -544,6 +1039,31 @@
             }
             validatedChange.reject("鍚屾壒娆″瓨鍦ㄨ鎷掔粷鍙樻洿锛屾湭鎵ц鍐欏叆");
         }
+    }
+
+    private void markWriteFailure(List<ValidatedChange> validatedChanges, RuntimeException exception) {
+        String reason = exception.getMessage() == null ? "鐩爣鍐欏叆澶辫触" : exception.getMessage();
+        for (ValidatedChange validatedChange : validatedChanges) {
+            if (ChangeStatus.NO_CHANGE.equals(validatedChange.getStatus())) {
+                continue;
+            }
+            validatedChange.fail(reason);
+        }
+    }
+
+    private List<AiAutoTuneChange> buildFailedRollbackChanges(Long rollbackJobId,
+                                                              List<AiAutoTuneChange> sourceChanges,
+                                                              RuntimeException exception,
+                                                              Date now) {
+        String reason = exception.getMessage() == null ? "鍥炴粴鍐欏叆澶辫触" : exception.getMessage();
+        List<AiAutoTuneChange> rollbackChanges = new ArrayList<>();
+        for (AiAutoTuneChange sourceChange : sourceChanges) {
+            AiAutoTuneChange rollbackChange = buildRollbackChange(rollbackJobId, sourceChange, now);
+            rollbackChange.setResultStatus(ChangeStatus.FAILED.getCode());
+            rollbackChange.setRejectReason(reason);
+            rollbackChanges.add(rollbackChange);
+        }
+        return rollbackChanges;
     }
 
     private boolean hasRejectedChange(List<ValidatedChange> validatedChanges) {
@@ -580,12 +1100,16 @@
         }
     }
 
-    private String normalizeTargetId(String targetType, String targetId) {
+    private static String normalizeTargetId(String targetType, String targetId) {
         AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType);
         if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) {
             return "";
         }
-        return targetId == null ? "" : targetId.trim();
+        return normalizeText(targetId);
+    }
+
+    private static String normalizeText(String value) {
+        return value == null ? "" : value.trim();
     }
 
     private int readIntervalMinutes() {
@@ -598,6 +1122,14 @@
         } catch (Exception exception) {
             return 0;
         }
+    }
+
+    private AutoTuneControlModeSnapshot currentControlModeSnapshot() {
+        return autoTuneControlModeService.currentMode();
+    }
+
+    private boolean isAnalysisOnly(AutoTuneControlModeSnapshot controlMode) {
+        return controlMode == null || Boolean.TRUE.equals(controlMode.getAnalysisOnly());
     }
 
     private String toText(Integer value) {
@@ -636,6 +1168,15 @@
         return count;
     }
 
+    private boolean hasFailedChange(List<AiAutoTuneChange> changes) {
+        for (AiAutoTuneChange change : changes) {
+            if (ChangeStatus.FAILED.getCode().equals(change.getResultStatus())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private String firstRejectReason(List<AiAutoTuneChange> changes) {
         for (AiAutoTuneChange change : changes) {
             if (change.getRejectReason() != null && !change.getRejectReason().trim().isEmpty()) {
@@ -645,8 +1186,48 @@
         return null;
     }
 
+    private static class ApplyPersistenceResult {
+        private final List<AiAutoTuneChange> auditChanges;
+        private final boolean refreshConfigCache;
+
+        private ApplyPersistenceResult(List<AiAutoTuneChange> auditChanges, boolean refreshConfigCache) {
+            this.auditChanges = auditChanges == null ? new ArrayList<>() : auditChanges;
+            this.refreshConfigCache = refreshConfigCache;
+        }
+
+        public List<AiAutoTuneChange> getAuditChanges() {
+            return auditChanges;
+        }
+
+        public boolean isRefreshConfigCache() {
+            return refreshConfigCache;
+        }
+    }
+
+    private static class RollbackPersistenceResult {
+        private final List<AiAutoTuneChange> rollbackChanges;
+        private final boolean refreshConfigCache;
+
+        private RollbackPersistenceResult(List<AiAutoTuneChange> rollbackChanges, boolean refreshConfigCache) {
+            this.rollbackChanges = rollbackChanges == null ? new ArrayList<>() : rollbackChanges;
+            this.refreshConfigCache = refreshConfigCache;
+        }
+
+        public List<AiAutoTuneChange> getRollbackChanges() {
+            return rollbackChanges;
+        }
+
+        public boolean isRefreshConfigCache() {
+            return refreshConfigCache;
+        }
+    }
+
     private static class ValidatedChange {
         private final AutoTuneChangeCommand command;
+        private final String targetType;
+        private final String targetId;
+        private final String targetKey;
+        private final String rawRequestedValue;
         private AutoTuneRuleDefinition.Rule rule;
         private String oldValue;
         private String requestedValue;
@@ -658,6 +1239,10 @@
 
         private ValidatedChange(AutoTuneChangeCommand command) {
             this.command = command == null ? new AutoTuneChangeCommand() : command;
+            this.targetType = normalizeText(this.command.getTargetType());
+            this.targetId = normalizeTargetId(this.targetType, this.command.getTargetId());
+            this.targetKey = normalizeText(this.command.getTargetKey());
+            this.rawRequestedValue = this.command.getNewValue();
         }
 
         private ValidatedChange reject(String reason) {
@@ -665,6 +1250,12 @@
             this.rejectReason = reason;
             this.appliedValue = null;
             return this;
+        }
+
+        private void fail(String reason) {
+            this.status = ChangeStatus.FAILED;
+            this.rejectReason = reason;
+            this.appliedValue = null;
         }
 
         private ValidatedChange accept(ChangeStatus status, String reason) {
@@ -680,6 +1271,22 @@
             return command;
         }
 
+        public String getTargetType() {
+            return targetType;
+        }
+
+        public String getTargetId() {
+            return targetId;
+        }
+
+        public String getTargetKey() {
+            return targetKey;
+        }
+
+        public String getRawRequestedValue() {
+            return rawRequestedValue;
+        }
+
         public AutoTuneRuleDefinition.Rule getRule() {
             return rule;
         }

--
Gitblit v1.9.1