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