package com.zy.ai.service.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
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;
|
import com.zy.ai.domain.autotune.AutoTuneTriggerType;
|
import com.zy.ai.entity.AiAutoTuneChange;
|
import com.zy.ai.entity.AiAutoTuneJob;
|
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.WrkMast;
|
import com.zy.asrs.service.BasCrnpService;
|
import com.zy.asrs.service.BasDualCrnpService;
|
import com.zy.asrs.service.BasStationService;
|
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.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 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;
|
@Autowired
|
private AiAutoTuneChangeService aiAutoTuneChangeService;
|
@Autowired
|
private ConfigService configService;
|
@Autowired
|
private AutoTuneControlModeService autoTuneControlModeService;
|
@Autowired
|
private BasStationService basStationService;
|
@Autowired
|
private BasCrnpService basCrnpService;
|
@Autowired
|
private BasDualCrnpService basDualCrnpService;
|
@Autowired
|
private WrkMastService wrkMastService;
|
@Autowired
|
private PlatformTransactionManager transactionManager;
|
@Autowired
|
private RedisUtil redisUtil;
|
|
@Override
|
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);
|
|
if (dryRun) {
|
return applyDryRun(safeRequest, job, now, controlMode);
|
}
|
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);
|
}
|
|
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);
|
}
|
|
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
|
public AutoTuneApplyResult rollbackLastSuccessfulJob(String reason) {
|
AutoTuneControlModeSnapshot controlMode = currentControlModeSnapshot();
|
Date now = new Date();
|
AiAutoTuneJob rollbackJob = createRollbackJob(reason, now);
|
if (isAnalysisOnly(controlMode)) {
|
return rejectRollbackForAnalysisOnly(rollbackJob, now, controlMode);
|
}
|
|
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);
|
}
|
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);
|
}
|
}
|
|
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) {
|
List<ValidatedChange> result = new ArrayList<>();
|
if (request.getChanges() == null || request.getChanges().isEmpty()) {
|
return result;
|
}
|
for (AutoTuneChangeCommand command : request.getChanges()) {
|
result.add(validateChange(command, dryRun, now));
|
}
|
return result;
|
}
|
|
private ValidatedChange validateChange(AutoTuneChangeCommand command, boolean dryRun, Date now) {
|
ValidatedChange validatedChange = new ValidatedChange(command);
|
AutoTuneRuleDefinition.Rule rule = AutoTuneRuleDefinition.findRule(
|
validatedChange.getTargetType(),
|
validatedChange.getTargetKey()
|
);
|
if (rule == null) {
|
return validatedChange.reject("不支持的调参目标: "
|
+ validatedChange.getTargetType() + "/" + validatedChange.getTargetKey());
|
}
|
validatedChange.setRule(rule);
|
|
Integer requestedValue = parseRequestedInt(validatedChange.getRawRequestedValue());
|
if (requestedValue == null) {
|
return validatedChange.reject(validatedChange.getTargetKey() + " 必须为整数");
|
}
|
validatedChange.setRequestedIntValue(requestedValue);
|
validatedChange.setRequestedValue(String.valueOf(requestedValue));
|
|
CurrentValue currentValue = readCurrentValueForValidation(validatedChange, rule);
|
if (currentValue.getRejectReason() != null) {
|
return validatedChange.reject(currentValue.getRejectReason());
|
}
|
validatedChange.setOldValue(currentValue.getOldValue());
|
|
Integer maxValue = resolveMaxValue(validatedChange, rule, requestedValue);
|
if (maxValue == null) {
|
return validatedChange.reject("站点 " + validatedChange.getTargetId()
|
+ " 缺少 outBufferCapacity,无法证明 outTaskLimit 上限");
|
}
|
if (requestedValue < rule.getMinValue() || requestedValue > maxValue) {
|
return validatedChange.reject(validatedChange.getTargetKey() + " 必须在 "
|
+ rule.getMinValue() + "~" + maxValue + " 范围内");
|
}
|
|
if (Objects.equals(currentValue.getNumericValue(), requestedValue)) {
|
return validatedChange.accept(ChangeStatus.NO_CHANGE, null);
|
}
|
int step = Math.abs(requestedValue - currentValue.getNumericValue());
|
if (step > rule.getMaxStep()) {
|
return validatedChange.reject(validatedChange.getTargetKey() + " 单次调整步长不能超过 " + rule.getMaxStep());
|
}
|
|
Date cooldownExpireTime = findCooldownExpireTime(validatedChange, now);
|
if (cooldownExpireTime != null) {
|
validatedChange.setCooldownExpireTime(cooldownExpireTime);
|
return validatedChange.reject("目标仍在冷却期,冷却截止时间: " + cooldownExpireTime);
|
}
|
|
Date nextCooldownExpireTime = new Date(now.getTime() + rule.getCooldownMinutes() * 60_000L);
|
validatedChange.setCooldownExpireTime(nextCooldownExpireTime);
|
return validatedChange.accept(dryRun ? ChangeStatus.DRY_RUN : ChangeStatus.PENDING, null);
|
}
|
|
private CurrentValue readCurrentValueForValidation(ValidatedChange validatedChange, AutoTuneRuleDefinition.Rule rule) {
|
AutoTuneTargetType targetType = rule.getTargetType();
|
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", validatedChange.getTargetKey()).last("limit 1"));
|
if (config == null) {
|
return CurrentValue.rejected("运行参数不存在: " + validatedChange.getTargetKey());
|
}
|
return numericCurrentValue(config.getValue(), validatedChange.getTargetKey());
|
}
|
if (AutoTuneTargetType.STATION.equals(targetType)) {
|
BasStation station = basStationService.getById(targetId);
|
if (station == null) {
|
return CurrentValue.rejected("站点不存在: " + validatedChange.getTargetId());
|
}
|
return stationOutTaskLimitCurrentValue(station.getOutTaskLimit(), validatedChange.getTargetKey());
|
}
|
if (AutoTuneTargetType.CRN.equals(targetType)) {
|
BasCrnp crnp = basCrnpService.getById(targetId);
|
if (crnp == null) {
|
return CurrentValue.rejected("堆垛机不存在: " + validatedChange.getTargetId());
|
}
|
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("双工位堆垛机不存在: " + validatedChange.getTargetId());
|
}
|
Integer value = "maxOutTask".equals(validatedChange.getTargetKey()) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask();
|
return numericCurrentValue(toText(value), validatedChange.getTargetKey());
|
}
|
|
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()) {
|
return CurrentValue.rejected(targetKey + " 当前值为空,无法计算步长");
|
}
|
try {
|
Integer parsedValue = Integer.valueOf(oldValue.trim());
|
return CurrentValue.accepted(oldValue, parsedValue);
|
} catch (Exception exception) {
|
return CurrentValue.rejected(targetKey + " 当前值不是整数,无法计算步长");
|
}
|
}
|
|
private Integer resolveMaxValue(ValidatedChange validatedChange,
|
AutoTuneRuleDefinition.Rule rule,
|
Integer requestedValue) {
|
if (!rule.isDynamicMaxValue()) {
|
return rule.getMaxValue();
|
}
|
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, station.getOutBufferCapacity());
|
}
|
|
private Date findCooldownExpireTime(ValidatedChange validatedChange, Date now) {
|
List<AiAutoTuneChange> recentChanges = aiAutoTuneChangeService.list(
|
new QueryWrapper<AiAutoTuneChange>()
|
.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")
|
.last("limit 1")
|
);
|
if (recentChanges == null || recentChanges.isEmpty()) {
|
return null;
|
}
|
AiAutoTuneChange latestChange = recentChanges.get(0);
|
Date cooldownExpireTime = latestChange.getCooldownExpireTime();
|
if (cooldownExpireTime == null || !cooldownExpireTime.after(now)) {
|
return null;
|
}
|
return cooldownExpireTime;
|
}
|
|
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;
|
}
|
writeValue(
|
validatedChange.getTargetType(),
|
validatedChange.getTargetId(),
|
validatedChange.getTargetKey(),
|
validatedChange.getRequestedValue()
|
);
|
validatedChange.accept(ChangeStatus.SUCCESS, null);
|
validatedChange.setAppliedValue(validatedChange.getRequestedValue());
|
if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(validatedChange.getTargetType())) {
|
refreshConfigCache = true;
|
}
|
}
|
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) {
|
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(normalizedTargetKey, value)) {
|
throw new IllegalStateException("保存运行参数失败: " + normalizedTargetKey);
|
}
|
return;
|
}
|
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("保存站点参数失败: " + normalizedTargetId + "/" + normalizedTargetKey);
|
}
|
return;
|
}
|
if (AutoTuneTargetType.CRN.equals(parsedTargetType)) {
|
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("保存堆垛机参数失败: " + normalizedTargetId + "/" + normalizedTargetKey);
|
}
|
return;
|
}
|
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("保存双工位堆垛机参数失败: " + normalizedTargetId + "/" + normalizedTargetKey);
|
}
|
}
|
|
private String readCurrentValue(String targetType, String targetId, String targetKey) {
|
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", normalizedTargetKey).last("limit 1"));
|
return config == null ? null : config.getValue();
|
}
|
if (AutoTuneTargetType.STATION.equals(parsedTargetType)) {
|
BasStation station = basStationService.getById(parsedTargetId);
|
return station == null ? null : toText(station.getOutTaskLimit());
|
}
|
if (AutoTuneTargetType.CRN.equals(parsedTargetType)) {
|
BasCrnp crnp = basCrnpService.getById(parsedTargetId);
|
if (crnp == null) {
|
return null;
|
}
|
return toText("maxOutTask".equals(normalizedTargetKey) ? crnp.getMaxOutTask() : crnp.getMaxInTask());
|
}
|
BasDualCrnp dualCrnp = basDualCrnpService.getById(parsedTargetId);
|
if (dualCrnp == null) {
|
return null;
|
}
|
return toText("maxOutTask".equals(normalizedTargetKey) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask());
|
}
|
|
private AiAutoTuneJob createJob(AutoTuneApplyRequest request, boolean dryRun, Date now) {
|
AiAutoTuneJob job = new AiAutoTuneJob();
|
job.setTriggerType(AutoTuneTriggerType.normalize(request.getTriggerType()));
|
job.setStatus(AutoTuneJobStatus.RUNNING.getCode());
|
job.setStartTime(now);
|
job.setHasActiveTasks(resolveHasActiveTasksForAudit());
|
job.setPromptSceneCode(PROMPT_SCENE_CODE);
|
job.setSummary(dryRun ? "AI自动调参 dry-run: " + safeReason(request.getReason()) : safeReason(request.getReason()));
|
job.setIntervalBefore(readIntervalMinutes());
|
job.setSuccessCount(0);
|
job.setRejectCount(0);
|
job.setLlmCallCount(0);
|
job.setPromptTokens(0);
|
job.setCompletionTokens(0);
|
job.setTotalTokens(0);
|
job.setCreateTime(now);
|
return job;
|
}
|
|
private AiAutoTuneJob createRollbackJob(String reason, Date now) {
|
AiAutoTuneJob job = new AiAutoTuneJob();
|
job.setTriggerType(AutoTuneTriggerType.ROLLBACK.getCode());
|
job.setStatus(AutoTuneJobStatus.RUNNING.getCode());
|
job.setStartTime(now);
|
job.setHasActiveTasks(resolveHasActiveTasksForAudit());
|
job.setPromptSceneCode(PROMPT_SCENE_CODE);
|
job.setSummary(safeReason(reason));
|
job.setIntervalBefore(readIntervalMinutes());
|
job.setSuccessCount(0);
|
job.setRejectCount(0);
|
job.setLlmCallCount(0);
|
job.setPromptTokens(0);
|
job.setCompletionTokens(0);
|
job.setTotalTokens(0);
|
job.setCreateTime(now);
|
return job;
|
}
|
|
private int resolveHasActiveTasksForAudit() {
|
if (wrkMastService == null) {
|
LOGGER.warn("AI自动调参审计无法获取 WrkMastService,hasActiveTasks 按 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,
|
AutoTuneApplyRequest request,
|
List<AiAutoTuneChange> auditChanges,
|
boolean dryRun,
|
Date now) {
|
int successCount = countAccepted(auditChanges);
|
int rejectCount = countRejected(auditChanges);
|
job.setFinishTime(now);
|
job.setSuccessCount(successCount);
|
job.setRejectCount(rejectCount);
|
job.setIntervalAfter(readIntervalMinutes());
|
job.setStatus(resolveJobStatus(auditChanges, dryRun));
|
job.setSummary(buildSummary(request.getReason(), successCount, rejectCount, dryRun));
|
if (rejectCount > 0) {
|
job.setErrorMessage(firstRejectReason(auditChanges));
|
}
|
}
|
|
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);
|
job.setFinishTime(now);
|
job.setSuccessCount(successCount);
|
job.setRejectCount(rejectCount);
|
job.setIntervalAfter(readIntervalMinutes());
|
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));
|
}
|
}
|
|
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);
|
if (rejectedCount == auditChanges.size()) {
|
return AutoTuneJobStatus.REJECTED.getCode();
|
}
|
if (rejectedCount > 0) {
|
return dryRun ? AutoTuneJobStatus.PARTIAL_SUCCESS.getCode() : AutoTuneJobStatus.REJECTED.getCode();
|
}
|
if (acceptedCount == 0) {
|
return AutoTuneJobStatus.NO_CHANGE.getCode();
|
}
|
boolean allNoChange = true;
|
for (AiAutoTuneChange change : auditChanges) {
|
if (!ChangeStatus.NO_CHANGE.getCode().equals(change.getResultStatus())) {
|
allNoChange = false;
|
break;
|
}
|
}
|
return allNoChange ? AutoTuneJobStatus.NO_CHANGE.getCode() : AutoTuneJobStatus.SUCCESS.getCode();
|
}
|
|
private List<AiAutoTuneChange> buildAuditChanges(Long jobId, List<ValidatedChange> validatedChanges, Date now) {
|
List<AiAutoTuneChange> auditChanges = new ArrayList<>();
|
for (ValidatedChange validatedChange : validatedChanges) {
|
AiAutoTuneChange change = new AiAutoTuneChange();
|
change.setJobId(jobId);
|
change.setTargetType(validatedChange.getTargetType());
|
change.setTargetId(validatedChange.getTargetId());
|
change.setTargetKey(validatedChange.getTargetKey());
|
change.setOldValue(validatedChange.getOldValue());
|
change.setRequestedValue(validatedChange.getRequestedValue() == null
|
? validatedChange.getRawRequestedValue()
|
: validatedChange.getRequestedValue());
|
change.setAppliedValue(validatedChange.getAppliedValue());
|
change.setResultStatus(validatedChange.getStatus().getCode());
|
change.setRejectReason(validatedChange.getRejectReason());
|
change.setCooldownExpireTime(validatedChange.getCooldownExpireTime());
|
change.setCreateTime(now);
|
auditChanges.add(change);
|
}
|
return auditChanges;
|
}
|
|
private AiAutoTuneChange buildRollbackChange(Long jobId, AiAutoTuneChange sourceChange, Date now) {
|
AiAutoTuneChange rollbackChange = new AiAutoTuneChange();
|
rollbackChange.setJobId(jobId);
|
rollbackChange.setTargetType(sourceChange.getTargetType());
|
rollbackChange.setTargetId(sourceChange.getTargetId());
|
rollbackChange.setTargetKey(sourceChange.getTargetKey());
|
rollbackChange.setRequestedValue(sourceChange.getOldValue());
|
rollbackChange.setCreateTime(now);
|
return rollbackChange;
|
}
|
|
private List<AiAutoTuneChange> findLatestSuccessfulChanges() {
|
List<AiAutoTuneChange> successfulChanges = aiAutoTuneChangeService.list(
|
new QueryWrapper<AiAutoTuneChange>()
|
.eq("result_status", ChangeStatus.SUCCESS.getCode())
|
.orderByDesc("create_time")
|
.orderByDesc("id")
|
);
|
if (successfulChanges == null || successfulChanges.isEmpty()) {
|
return new ArrayList<>();
|
}
|
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())
|
.eq("result_status", ChangeStatus.SUCCESS.getCode())
|
.orderByAsc("id")
|
);
|
if (changes != null && !changes.isEmpty()) {
|
return changes;
|
}
|
}
|
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,
|
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());
|
result.setRejectCount(job.getRejectCount());
|
result.setChanges(changes);
|
return result;
|
}
|
|
private void markAcceptedChangesAsBatchRejected(List<ValidatedChange> validatedChanges) {
|
for (ValidatedChange validatedChange : validatedChanges) {
|
if (ChangeStatus.REJECTED.equals(validatedChange.getStatus())) {
|
continue;
|
}
|
if (ChangeStatus.NO_CHANGE.equals(validatedChange.getStatus())) {
|
continue;
|
}
|
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) {
|
for (ValidatedChange validatedChange : validatedChanges) {
|
if (ChangeStatus.REJECTED.equals(validatedChange.getStatus())) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
private Integer parseRequestedInt(String value) {
|
if (value == null || value.trim().isEmpty()) {
|
return null;
|
}
|
try {
|
return Integer.valueOf(value.trim());
|
} catch (Exception exception) {
|
return null;
|
}
|
}
|
|
private Integer parseTargetId(String targetId, AutoTuneTargetType targetType) {
|
if (AutoTuneTargetType.SYS_CONFIG.equals(targetType)) {
|
return null;
|
}
|
if (targetId == null || targetId.trim().isEmpty()) {
|
return null;
|
}
|
try {
|
return Integer.valueOf(targetId.trim());
|
} catch (Exception exception) {
|
return null;
|
}
|
}
|
|
private static String normalizeTargetId(String targetType, String targetId) {
|
AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType);
|
if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) {
|
return "";
|
}
|
return normalizeText(targetId);
|
}
|
|
private static String normalizeText(String value) {
|
return value == null ? "" : value.trim();
|
}
|
|
private int readIntervalMinutes() {
|
String value = configService.getConfigValue("aiAutoTuneIntervalMinutes", null);
|
if (value == null || value.trim().isEmpty()) {
|
return 0;
|
}
|
try {
|
return Integer.parseInt(value.trim());
|
} 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) {
|
return value == null ? null : String.valueOf(value);
|
}
|
|
private String safeReason(String reason) {
|
return reason == null ? "" : reason.trim();
|
}
|
|
private String buildSummary(String reason, int successCount, int rejectCount, boolean dryRun) {
|
String prefix = dryRun ? "AI自动调参 dry-run" : "AI自动调参";
|
return prefix + ",成功 " + successCount + " 项,拒绝 " + rejectCount + " 项。原因: " + safeReason(reason);
|
}
|
|
private int countAccepted(List<AiAutoTuneChange> changes) {
|
int count = 0;
|
for (AiAutoTuneChange change : changes) {
|
if (ChangeStatus.SUCCESS.getCode().equals(change.getResultStatus())
|
|| ChangeStatus.DRY_RUN.getCode().equals(change.getResultStatus())
|
|| ChangeStatus.NO_CHANGE.getCode().equals(change.getResultStatus())) {
|
count++;
|
}
|
}
|
return count;
|
}
|
|
private int countRejected(List<AiAutoTuneChange> changes) {
|
int count = 0;
|
for (AiAutoTuneChange change : changes) {
|
if (ChangeStatus.REJECTED.getCode().equals(change.getResultStatus())
|
|| ChangeStatus.FAILED.getCode().equals(change.getResultStatus())) {
|
count++;
|
}
|
}
|
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()) {
|
return change.getRejectReason();
|
}
|
}
|
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;
|
private String appliedValue;
|
private Integer requestedIntValue;
|
private ChangeStatus status = ChangeStatus.REJECTED;
|
private String rejectReason;
|
private Date cooldownExpireTime;
|
|
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) {
|
this.status = ChangeStatus.REJECTED;
|
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) {
|
this.status = status;
|
this.rejectReason = reason;
|
if (ChangeStatus.NO_CHANGE.equals(status)) {
|
this.appliedValue = this.oldValue;
|
}
|
return this;
|
}
|
|
public AutoTuneChangeCommand getCommand() {
|
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;
|
}
|
|
public void setRule(AutoTuneRuleDefinition.Rule rule) {
|
this.rule = rule;
|
}
|
|
public String getOldValue() {
|
return oldValue;
|
}
|
|
public void setOldValue(String oldValue) {
|
this.oldValue = oldValue;
|
}
|
|
public String getRequestedValue() {
|
return requestedValue;
|
}
|
|
public void setRequestedValue(String requestedValue) {
|
this.requestedValue = requestedValue;
|
}
|
|
public String getAppliedValue() {
|
return appliedValue;
|
}
|
|
public void setAppliedValue(String appliedValue) {
|
this.appliedValue = appliedValue;
|
}
|
|
public Integer getRequestedIntValue() {
|
return requestedIntValue;
|
}
|
|
public void setRequestedIntValue(Integer requestedIntValue) {
|
this.requestedIntValue = requestedIntValue;
|
}
|
|
public ChangeStatus getStatus() {
|
return status;
|
}
|
|
public String getRejectReason() {
|
return rejectReason;
|
}
|
|
public Date getCooldownExpireTime() {
|
return cooldownExpireTime;
|
}
|
|
public void setCooldownExpireTime(Date cooldownExpireTime) {
|
this.cooldownExpireTime = cooldownExpireTime;
|
}
|
}
|
|
private static class CurrentValue {
|
private final String oldValue;
|
private final Integer numericValue;
|
private final String rejectReason;
|
|
private CurrentValue(String oldValue, Integer numericValue, String rejectReason) {
|
this.oldValue = oldValue;
|
this.numericValue = numericValue;
|
this.rejectReason = rejectReason;
|
}
|
|
private static CurrentValue accepted(String oldValue, Integer numericValue) {
|
return new CurrentValue(oldValue, numericValue, null);
|
}
|
|
private static CurrentValue rejected(String rejectReason) {
|
return new CurrentValue(null, null, rejectReason);
|
}
|
|
public String getOldValue() {
|
return oldValue;
|
}
|
|
public Integer getNumericValue() {
|
return numericValue;
|
}
|
|
public String getRejectReason() {
|
return rejectReason;
|
}
|
}
|
|
private enum ChangeStatus {
|
PENDING("pending"),
|
SUCCESS("success"),
|
REJECTED("rejected"),
|
FAILED("failed"),
|
DRY_RUN("dry_run"),
|
NO_CHANGE("no_change");
|
|
private final String code;
|
|
ChangeStatus(String code) {
|
this.code = code;
|
}
|
|
public String getCode() {
|
return code;
|
}
|
}
|
}
|