From 8e8069362418d8db5179f7716d71d9fb3e4a0034 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期一, 27 四月 2026 13:05:42 +0800
Subject: [PATCH] fix: make auto tune apply writes auditable atomically
---
src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java | 201 +++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 181 insertions(+), 20 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 e6a1e20..f443517 100644
--- a/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
+++ b/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
@@ -22,6 +22,8 @@
import com.zy.asrs.service.BasDualCrnpService;
import com.zy.asrs.service.BasStationService;
import com.zy.asrs.service.StationFlowCapacityService;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.RedisKeyType;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -35,12 +37,15 @@
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 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 = "AI鑷姩璋冨弬姝e湪鎵ц锛岃绋嶅悗閲嶈瘯";
@Autowired
private AiAutoTuneJobService aiAutoTuneJobService;
@@ -58,6 +63,8 @@
private StationFlowCapacityService stationFlowCapacityService;
@Autowired
private PlatformTransactionManager transactionManager;
+ @Autowired
+ private RedisUtil redisUtil;
@Override
public AutoTuneApplyResult apply(AutoTuneApplyRequest request) {
@@ -67,26 +74,114 @@
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);
}
- if (!dryRun && !hasRejectedChange) {
- try {
- applyValidatedChangesInTransaction(validatedChanges);
- } catch (RuntimeException exception) {
- markWriteFailure(validatedChanges, exception);
- }
+ return applyRealWithLock(safeRequest, job, now);
+ }
+
+ private AutoTuneApplyResult applyDryRun(AutoTuneApplyRequest request, AiAutoTuneJob job, Date now) {
+ List<ValidatedChange> validatedChanges = validateChanges(request, true, now);
+ ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+ job,
+ request,
+ validatedChanges,
+ true,
+ now,
+ false
+ );
+ return buildResult(job, persistenceResult.getAuditChanges(), true);
+ }
+
+ private AutoTuneApplyResult applyRealWithLock(AutoTuneApplyRequest request, AiAutoTuneJob job, Date now) {
+ if (request.getChanges() == null || request.getChanges().isEmpty()) {
+ ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+ job,
+ request,
+ new ArrayList<>(),
+ false,
+ now,
+ false
+ );
+ return buildResult(job, persistenceResult.getAuditChanges(), false);
}
- 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 rejectRealApplyForBusyLock(request, job, now);
}
- 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);
+ }
+
+ try {
+ ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+ job,
+ request,
+ validatedChanges,
+ false,
+ now,
+ true
+ );
+ refreshSystemConfigCacheIfNeeded(persistenceResult);
+ return buildResult(job, persistenceResult.getAuditChanges(), false);
+ } catch (RuntimeException exception) {
+ markWriteFailure(validatedChanges, exception);
+ ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+ job,
+ request,
+ validatedChanges,
+ false,
+ now,
+ false
+ );
+ return buildResult(job, persistenceResult.getAuditChanges(), false);
+ }
+ } finally {
+ redisUtil.compareAndDelete(lockKey, lockToken);
+ }
+ }
+
+ private AutoTuneApplyResult rejectRealApplyForBusyLock(AutoTuneApplyRequest request, AiAutoTuneJob job, Date now) {
+ List<ValidatedChange> validatedChanges = buildLockBusyChanges(request);
+ ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
+ job,
+ request,
+ validatedChanges,
+ false,
+ now,
+ false
+ );
+ return buildResult(job, persistenceResult.getAuditChanges(), false);
+ }
+
+ 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
@@ -279,12 +374,41 @@
return cooldownExpireTime;
}
- private void applyValidatedChangesInTransaction(List<ValidatedChange> validatedChanges) {
+ private ApplyPersistenceResult persistApplyResultInTransaction(AiAutoTuneJob job,
+ AutoTuneApplyRequest request,
+ List<ValidatedChange> validatedChanges,
+ boolean dryRun,
+ Date now,
+ boolean writeTargets) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
- transactionTemplate.executeWithoutResult(status -> applyValidatedChanges(validatedChanges));
+ return transactionTemplate.execute(status -> persistApplyResult(
+ job,
+ request,
+ validatedChanges,
+ dryRun,
+ now,
+ writeTargets
+ ));
}
- private void applyValidatedChanges(List<ValidatedChange> validatedChanges) {
+ private ApplyPersistenceResult persistApplyResult(AiAutoTuneJob job,
+ AutoTuneApplyRequest request,
+ List<ValidatedChange> validatedChanges,
+ boolean dryRun,
+ Date now,
+ boolean writeTargets) {
+ 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 boolean applyValidatedChanges(List<ValidatedChange> validatedChanges) {
boolean refreshConfigCache = false;
for (ValidatedChange validatedChange : validatedChanges) {
if (ChangeStatus.NO_CHANGE.equals(validatedChange.getStatus())) {
@@ -302,7 +426,26 @@
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 refreshSystemConfigCacheIfNeeded(ApplyPersistenceResult persistenceResult) {
+ if (persistenceResult != null && persistenceResult.isRefreshConfigCache()) {
configService.refreshSystemConfigCache();
}
}
@@ -759,6 +902,24 @@
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 ValidatedChange {
private final AutoTuneChangeCommand command;
private final String targetType;
--
Gitblit v1.9.1