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.system.entity.Config; import com.zy.system.service.ConfigService; 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; @Service("autoTuneApplyService") public class AutoTuneApplyServiceImpl implements AutoTuneApplyService { private static final String PROMPT_SCENE_CODE = "auto_tune_apply"; private static final String DIRECTION_OUT = "OUT"; @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; @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); aiAutoTuneJobService.save(job); List validatedChanges = validateChanges(safeRequest, dryRun, now); boolean hasRejectedChange = hasRejectedChange(validatedChanges); if (!dryRun && hasRejectedChange) { markAcceptedChangesAsBatchRejected(validatedChanges); } if (!dryRun && !hasRejectedChange) { try { applyValidatedChangesInTransaction(validatedChanges); } catch (RuntimeException exception) { markWriteFailure(validatedChanges, exception); } } List auditChanges = buildAuditChanges(job.getId(), validatedChanges, now); if (!auditChanges.isEmpty()) { aiAutoTuneChangeService.saveBatch(auditChanges); } finishJob(job, safeRequest, auditChanges, dryRun, now); aiAutoTuneJobService.updateById(job); return buildResult(job, auditChanges, dryRun); } @Override public AutoTuneApplyResult rollbackLastSuccessfulJob(String reason) { Date now = new Date(); AiAutoTuneJob rollbackJob = createRollbackJob(reason, now); aiAutoTuneJobService.save(rollbackJob); List sourceChanges = findLatestSuccessfulChanges(); if (sourceChanges.isEmpty()) { rollbackJob.setStatus(AutoTuneJobStatus.REJECTED.getCode()); rollbackJob.setFinishTime(now); rollbackJob.setRejectCount(0); rollbackJob.setSuccessCount(0); rollbackJob.setSummary("未找到可回滚的成功调参记录"); rollbackJob.setErrorMessage("未找到可回滚的成功调参记录"); aiAutoTuneJobService.updateById(rollbackJob); return buildResult(rollbackJob, new ArrayList<>(), false); } List rollbackChanges = new ArrayList<>(); try { rollbackChanges = rollbackChangesInTransaction(rollbackJob.getId(), sourceChanges, now); } catch (RuntimeException exception) { rollbackChanges = buildFailedRollbackChanges(rollbackJob.getId(), sourceChanges, exception, now); } aiAutoTuneChangeService.saveBatch(rollbackChanges); finishRollbackJob(rollbackJob, rollbackChanges, now); aiAutoTuneJobService.updateById(rollbackJob); return buildResult(rollbackJob, rollbackChanges, 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 void applyValidatedChangesInTransaction(List validatedChanges) { TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); transactionTemplate.executeWithoutResult(status -> applyValidatedChanges(validatedChanges)); } private void 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; } } if (refreshConfigCache) { configService.refreshSystemConfigCache(); } } private List rollbackChangesInTransaction(Long rollbackJobId, List sourceChanges, Date now) { TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); return transactionTemplate.execute(status -> rollbackChanges(rollbackJobId, sourceChanges, now)); } private List 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); } if (refreshConfigCache) { configService.refreshSystemConfigCache(); } return rollbackChanges; } 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 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; } } }