From f955390da5d6fe785bfb9828b44f1603cd4ff0b8 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期一, 27 四月 2026 11:07:25 +0800
Subject: [PATCH] fix: harden auto tune apply audit and rollback

---
 src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java |  165 +++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 129 insertions(+), 36 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..98a588a 100644
--- a/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
+++ b/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
@@ -26,12 +26,15 @@
 import com.zy.system.service.ConfigService;
 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.Date;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 @Service("autoTuneApplyService")
 public class AutoTuneApplyServiceImpl implements AutoTuneApplyService {
@@ -53,9 +56,10 @@
     private BasDualCrnpService basDualCrnpService;
     @Autowired
     private StationFlowCapacityService stationFlowCapacityService;
+    @Autowired(required = false)
+    private PlatformTransactionManager transactionManager;
 
     @Override
-    @Transactional
     public AutoTuneApplyResult apply(AutoTuneApplyRequest request) {
         AutoTuneApplyRequest safeRequest = request == null ? new AutoTuneApplyRequest() : request;
         boolean dryRun = Boolean.TRUE.equals(safeRequest.getDryRun());
@@ -69,7 +73,11 @@
             markAcceptedChangesAsBatchRejected(validatedChanges);
         }
         if (!dryRun && !hasRejectedChange) {
-            applyValidatedChanges(validatedChanges);
+            try {
+                applyValidatedChangesInTransaction(validatedChanges);
+            } catch (RuntimeException exception) {
+                markWriteFailure(validatedChanges, exception);
+            }
         }
 
         List<AiAutoTuneChange> auditChanges = buildAuditChanges(job.getId(), validatedChanges, now);
@@ -82,7 +90,6 @@
     }
 
     @Override
-    @Transactional
     public AutoTuneApplyResult rollbackLastSuccessfulJob(String reason) {
         Date now = new Date();
         AiAutoTuneJob rollbackJob = createRollbackJob(reason, now);
@@ -101,30 +108,10 @@
         }
 
         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());
-            }
-            rollbackChanges.add(rollbackChange);
-        }
-        if (refreshedConfigCache) {
-            configService.refreshSystemConfigCache();
+        try {
+            rollbackChanges = rollbackChangesInTransaction(rollbackJob.getId(), sourceChanges, now);
+        } catch (RuntimeException exception) {
+            rollbackChanges = buildFailedRollbackChanges(rollbackJob.getId(), sourceChanges, exception, now);
         }
         aiAutoTuneChangeService.saveBatch(rollbackChanges);
         finishRollbackJob(rollbackJob, rollbackChanges, now);
@@ -287,6 +274,15 @@
         return cooldownExpireTime;
     }
 
+    private void applyValidatedChangesInTransaction(List<ValidatedChange> validatedChanges) {
+        if (transactionManager == null) {
+            applyValidatedChanges(validatedChanges);
+            return;
+        }
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        transactionTemplate.executeWithoutResult(status -> applyValidatedChanges(validatedChanges));
+    }
+
     private void applyValidatedChanges(List<ValidatedChange> validatedChanges) {
         boolean refreshConfigCache = false;
         for (ValidatedChange validatedChange : validatedChanges) {
@@ -304,6 +300,41 @@
         if (refreshConfigCache) {
             configService.refreshSystemConfigCache();
         }
+    }
+
+    private List<AiAutoTuneChange> rollbackChangesInTransaction(Long rollbackJobId,
+                                                                List<AiAutoTuneChange> sourceChanges,
+                                                                Date now) {
+        if (transactionManager == null) {
+            return rollbackChanges(rollbackJobId, sourceChanges, now);
+        }
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        return transactionTemplate.execute(status -> rollbackChanges(rollbackJobId, sourceChanges, now));
+    }
+
+    private List<AiAutoTuneChange> 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);
+        }
+        if (refreshConfigCache) {
+            configService.refreshSystemConfigCache();
+        }
+        return rollbackChanges;
     }
 
     private void writeValue(String targetType, String targetId, String targetKey, String value) {
@@ -443,6 +474,9 @@
         if (auditChanges.isEmpty()) {
             return AutoTuneJobStatus.NO_CHANGE.getCode();
         }
+        if (hasFailedChange(auditChanges)) {
+            return AutoTuneJobStatus.FAILED.getCode();
+        }
         int rejectedCount = countRejected(auditChanges);
         int acceptedCount = countAccepted(auditChanges);
         if (rejectedCount == auditChanges.size()) {
@@ -497,17 +531,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())
@@ -519,6 +562,16 @@
             }
         }
         return new ArrayList<>();
+    }
+
+    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) {
@@ -544,6 +597,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) {
@@ -636,6 +714,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()) {
@@ -667,6 +754,12 @@
             return this;
         }
 
+        private void fail(String reason) {
+            this.status = ChangeStatus.FAILED;
+            this.rejectReason = reason;
+            this.appliedValue = null;
+        }
+
         private ValidatedChange accept(ChangeStatus status, String reason) {
             this.status = status;
             this.rejectReason = reason;

--
Gitblit v1.9.1