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.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.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.service.BasCrnpService; 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.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.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 异常"; @Autowired private AiAutoTuneJobService aiAutoTuneJobService; @Autowired private AiAutoTuneChangeService aiAutoTuneChangeService; @Autowired private ConfigService configService; @Autowired private BasStationService basStationService; @Autowired private BasCrnpService basCrnpService; @Autowired private BasDualCrnpService basDualCrnpService; @Autowired private StationFlowCapacityService stationFlowCapacityService; @Autowired private PlatformTransactionManager transactionManager; @Autowired private RedisUtil redisUtil; @Override public AutoTuneApplyResult apply(AutoTuneApplyRequest request) { AutoTuneApplyRequest safeRequest = request == null ? new AutoTuneApplyRequest() : request; boolean dryRun = Boolean.TRUE.equals(safeRequest.getDryRun()); Date now = new Date(); AiAutoTuneJob job = createJob(safeRequest, dryRun, now); if (dryRun) { return applyDryRun(safeRequest, job, now); } return applyRealWithLock(safeRequest, job, now); } private AutoTuneApplyResult applyDryRun(AutoTuneApplyRequest request, AiAutoTuneJob job, Date now) { List 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); } 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); } try { List 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 ); refreshSystemConfigCacheSafely(persistenceResult); return buildResult(job, persistenceResult.getAuditChanges(), false); } 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); } } finally { redisUtil.compareAndDelete(lockKey, lockToken); } } private AutoTuneApplyResult rejectRealApplyForUnavailableLock(AutoTuneApplyRequest request, AiAutoTuneJob job, Date now, String lockKey) { boolean lockKeyExists = redisUtil.hasKey(lockKey); LOGGER.warn("申请AI自动调参 apply 锁失败,lockKey={}, lockKeyExists={}", lockKey, lockKeyExists); List validatedChanges = buildLockBusyChanges(request); ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction( job, request, validatedChanges, false, now, false ); return buildResult(job, persistenceResult.getAuditChanges(), false); } private List buildLockBusyChanges(AutoTuneApplyRequest request) { List 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) { Date now = new Date(); AiAutoTuneJob rollbackJob = createRollbackJob(reason, now); 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); } try { List sourceChanges = findLatestSuccessfulChanges(); if (sourceChanges.isEmpty()) { persistNoRollbackSourceJobInTransaction(rollbackJob, now); return buildResult(rollbackJob, new ArrayList<>(), false); } try { RollbackPersistenceResult persistenceResult = persistRollbackResultInTransaction( rollbackJob, sourceChanges, now ); refreshRollbackConfigCacheSafely(persistenceResult); return buildResult(rollbackJob, persistenceResult.getRollbackChanges(), false); } 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); } } finally { redisUtil.compareAndDelete(lockKey, lockToken); } } private AutoTuneApplyResult rejectRollbackForUnavailableLock(String reason, Date now, String lockKey) { 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); } private List validateChanges(AutoTuneApplyRequest request, boolean dryRun, Date now) { List 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() + " 缺少 OUT 方向 bufferCapacity,无法证明 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().eq("code", validatedChange.getTargetKey()).last("limit 1")); if (config == null) { return CurrentValue.rejected("运行参数不存在: " + validatedChange.getTargetKey()); } return numericCurrentValue(config.getValue(), false, validatedChange.getTargetKey()); } if (AutoTuneTargetType.STATION.equals(targetType)) { BasStation station = basStationService.getById(targetId); if (station == null) { return CurrentValue.rejected("站点不存在: " + validatedChange.getTargetId()); } return numericCurrentValue(toText(station.getOutTaskLimit()), true, 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), false, 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), false, validatedChange.getTargetKey()); } private CurrentValue numericCurrentValue(String oldValue, boolean nullOrNegativeAsZero, 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(ValidatedChange validatedChange, AutoTuneRuleDefinition.Rule rule, Integer requestedValue) { if (!rule.isDynamicMaxValue()) { return rule.getMaxValue(); } Integer targetId = parseTargetId(validatedChange.getTargetId(), rule.getTargetType()); StationFlowCapacity capacity = stationFlowCapacityService.getOne( new QueryWrapper() .eq("station_id", targetId) .eq("direction_code", DIRECTION_OUT) .last("limit 1") ); if (capacity == null || capacity.getBufferCapacity() == null) { return requestedValue == 0 ? 0 : null; } return Math.max(0, capacity.getBufferCapacity()); } private Date findCooldownExpireTime(ValidatedChange validatedChange, Date now) { List recentChanges = aiAutoTuneChangeService.list( new QueryWrapper() .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 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 validatedChanges, boolean dryRun, Date now, boolean writeTargets) { saveJob(job); boolean refreshConfigCache = false; if (writeTargets) { refreshConfigCache = applyValidatedChanges(validatedChanges); } List 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 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 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 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 sourceChanges, RuntimeException exception, Date now) { TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); return transactionTemplate.execute(status -> { saveJob(rollbackJob); List 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 rollbackChanges = buildRollbackLockFailureChanges(rollbackJob.getId(), now); saveAuditChanges(rollbackChanges); finishRollbackJob(rollbackJob, rollbackChanges, now); updateJob(rollbackJob); return new RollbackPersistenceResult(rollbackChanges, false); }); } private RollbackPersistenceResult rollbackChanges(Long rollbackJobId, List sourceChanges, Date now) { List 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 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 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() .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() .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() .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().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(0); 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(0); 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 void finishJob(AiAutoTuneJob job, AutoTuneApplyRequest request, List 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 finishRollbackJob(AiAutoTuneJob job, List 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 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 buildAuditChanges(Long jobId, List validatedChanges, Date now) { List 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 findLatestSuccessfulChanges() { List successfulChanges = aiAutoTuneChangeService.list( new QueryWrapper() .eq("result_status", ChangeStatus.SUCCESS.getCode()) .orderByDesc("create_time") .orderByDesc("id") ); if (successfulChanges == null || successfulChanges.isEmpty()) { return new ArrayList<>(); } Set 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 changes = aiAutoTuneChangeService.list( new QueryWrapper() .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 changes, boolean dryRun) { AutoTuneApplyResult result = new AutoTuneApplyResult(); result.setDryRun(dryRun); result.setSuccess(AutoTuneJobStatus.SUCCESS.getCode().equals(job.getStatus()) || AutoTuneJobStatus.NO_CHANGE.getCode().equals(job.getStatus())); 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 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 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 buildFailedRollbackChanges(Long rollbackJobId, List sourceChanges, RuntimeException exception, Date now) { String reason = exception.getMessage() == null ? "回滚写入失败" : exception.getMessage(); List 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 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 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 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 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 changes) { for (AiAutoTuneChange change : changes) { if (ChangeStatus.FAILED.getCode().equals(change.getResultStatus())) { return true; } } return false; } private String firstRejectReason(List 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 auditChanges; private final boolean refreshConfigCache; private ApplyPersistenceResult(List auditChanges, boolean refreshConfigCache) { this.auditChanges = auditChanges == null ? new ArrayList<>() : auditChanges; this.refreshConfigCache = refreshConfigCache; } public List getAuditChanges() { return auditChanges; } public boolean isRefreshConfigCache() { return refreshConfigCache; } } private static class RollbackPersistenceResult { private final List rollbackChanges; private final boolean refreshConfigCache; private RollbackPersistenceResult(List rollbackChanges, boolean refreshConfigCache) { this.rollbackChanges = rollbackChanges == null ? new ArrayList<>() : rollbackChanges; this.refreshConfigCache = refreshConfigCache; } public List 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; } } }