| New file |
| | |
| | | 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.annotation.Transactional; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | @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; |
| | | |
| | | @Override |
| | | @Transactional |
| | | 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<ValidatedChange> validatedChanges = validateChanges(safeRequest, dryRun, now); |
| | | boolean hasRejectedChange = hasRejectedChange(validatedChanges); |
| | | if (!dryRun && hasRejectedChange) { |
| | | markAcceptedChangesAsBatchRejected(validatedChanges); |
| | | } |
| | | if (!dryRun && !hasRejectedChange) { |
| | | applyValidatedChanges(validatedChanges); |
| | | } |
| | | |
| | | List<AiAutoTuneChange> 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 |
| | | @Transactional |
| | | public AutoTuneApplyResult rollbackLastSuccessfulJob(String reason) { |
| | | Date now = new Date(); |
| | | AiAutoTuneJob rollbackJob = createRollbackJob(reason, now); |
| | | aiAutoTuneJobService.save(rollbackJob); |
| | | |
| | | List<AiAutoTuneChange> 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<AiAutoTuneChange> rollbackChanges = new ArrayList<>(); |
| | | boolean refreshedConfigCache = false; |
| | | for (AiAutoTuneChange sourceChange : sourceChanges) { |
| | | AiAutoTuneChange rollbackChange = buildRollbackChange(rollbackJob.getId(), sourceChange, now); |
| | | try { |
| | | String currentValue = readCurrentValue( |
| | | sourceChange.getTargetType(), |
| | | sourceChange.getTargetId(), |
| | | sourceChange.getTargetKey() |
| | | ); |
| | | rollbackChange.setOldValue(currentValue); |
| | | writeValue(sourceChange.getTargetType(), sourceChange.getTargetId(), sourceChange.getTargetKey(), sourceChange.getOldValue()); |
| | | if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(sourceChange.getTargetType())) { |
| | | refreshedConfigCache = true; |
| | | } |
| | | rollbackChange.setAppliedValue(sourceChange.getOldValue()); |
| | | rollbackChange.setResultStatus(ChangeStatus.SUCCESS.getCode()); |
| | | } catch (Exception exception) { |
| | | rollbackChange.setResultStatus(ChangeStatus.FAILED.getCode()); |
| | | rollbackChange.setRejectReason(exception.getMessage()); |
| | | } |
| | | rollbackChanges.add(rollbackChange); |
| | | } |
| | | if (refreshedConfigCache) { |
| | | configService.refreshSystemConfigCache(); |
| | | } |
| | | aiAutoTuneChangeService.saveBatch(rollbackChanges); |
| | | finishRollbackJob(rollbackJob, rollbackChanges, now); |
| | | aiAutoTuneJobService.updateById(rollbackJob); |
| | | return buildResult(rollbackJob, rollbackChanges, false); |
| | | } |
| | | |
| | | 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(command.getTargetType(), command.getTargetKey()); |
| | | if (rule == null) { |
| | | return validatedChange.reject("不支持的调参目标: " + command.getTargetType() + "/" + command.getTargetKey()); |
| | | } |
| | | validatedChange.setRule(rule); |
| | | |
| | | Integer requestedValue = parseRequestedInt(command.getNewValue()); |
| | | if (requestedValue == null) { |
| | | return validatedChange.reject(command.getTargetKey() + " 必须为整数"); |
| | | } |
| | | validatedChange.setRequestedIntValue(requestedValue); |
| | | validatedChange.setRequestedValue(String.valueOf(requestedValue)); |
| | | |
| | | CurrentValue currentValue = readCurrentValueForValidation(command, rule); |
| | | if (currentValue.getRejectReason() != null) { |
| | | return validatedChange.reject(currentValue.getRejectReason()); |
| | | } |
| | | validatedChange.setOldValue(currentValue.getOldValue()); |
| | | |
| | | Integer maxValue = resolveMaxValue(command, rule, requestedValue); |
| | | if (maxValue == null) { |
| | | return validatedChange.reject("站点 " + command.getTargetId() + " 缺少 OUT 方向 bufferCapacity,无法证明 outTaskLimit 上限"); |
| | | } |
| | | if (requestedValue < rule.getMinValue() || requestedValue > maxValue) { |
| | | return validatedChange.reject(command.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(command.getTargetKey() + " 单次调整步长不能超过 " + rule.getMaxStep()); |
| | | } |
| | | |
| | | Date cooldownExpireTime = findCooldownExpireTime(command, 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(AutoTuneChangeCommand command, AutoTuneRuleDefinition.Rule rule) { |
| | | AutoTuneTargetType targetType = rule.getTargetType(); |
| | | Integer targetId = parseTargetId(command.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", command.getTargetKey()).last("limit 1")); |
| | | if (config == null) { |
| | | return CurrentValue.rejected("运行参数不存在: " + command.getTargetKey()); |
| | | } |
| | | return numericCurrentValue(config.getValue(), false, command.getTargetKey()); |
| | | } |
| | | if (AutoTuneTargetType.STATION.equals(targetType)) { |
| | | BasStation station = basStationService.getById(targetId); |
| | | if (station == null) { |
| | | return CurrentValue.rejected("站点不存在: " + command.getTargetId()); |
| | | } |
| | | return numericCurrentValue(toText(station.getOutTaskLimit()), true, command.getTargetKey()); |
| | | } |
| | | if (AutoTuneTargetType.CRN.equals(targetType)) { |
| | | BasCrnp crnp = basCrnpService.getById(targetId); |
| | | if (crnp == null) { |
| | | return CurrentValue.rejected("堆垛机不存在: " + command.getTargetId()); |
| | | } |
| | | Integer value = "maxOutTask".equals(command.getTargetKey()) ? crnp.getMaxOutTask() : crnp.getMaxInTask(); |
| | | return numericCurrentValue(toText(value), false, command.getTargetKey()); |
| | | } |
| | | BasDualCrnp dualCrnp = basDualCrnpService.getById(targetId); |
| | | if (dualCrnp == null) { |
| | | return CurrentValue.rejected("双工位堆垛机不存在: " + command.getTargetId()); |
| | | } |
| | | Integer value = "maxOutTask".equals(command.getTargetKey()) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask(); |
| | | return numericCurrentValue(toText(value), false, command.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(AutoTuneChangeCommand command, |
| | | AutoTuneRuleDefinition.Rule rule, |
| | | Integer requestedValue) { |
| | | if (!rule.isDynamicMaxValue()) { |
| | | return rule.getMaxValue(); |
| | | } |
| | | Integer targetId = parseTargetId(command.getTargetId(), rule.getTargetType()); |
| | | StationFlowCapacity capacity = stationFlowCapacityService.getOne( |
| | | new QueryWrapper<StationFlowCapacity>() |
| | | .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(AutoTuneChangeCommand command, Date now) { |
| | | String targetId = normalizeTargetId(command.getTargetType(), command.getTargetId()); |
| | | List<AiAutoTuneChange> recentChanges = aiAutoTuneChangeService.list( |
| | | new QueryWrapper<AiAutoTuneChange>() |
| | | .eq("target_type", command.getTargetType()) |
| | | .eq("target_id", targetId) |
| | | .eq("target_key", command.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 applyValidatedChanges(List<ValidatedChange> validatedChanges) { |
| | | boolean refreshConfigCache = false; |
| | | for (ValidatedChange validatedChange : validatedChanges) { |
| | | if (ChangeStatus.NO_CHANGE.equals(validatedChange.getStatus())) { |
| | | continue; |
| | | } |
| | | AutoTuneChangeCommand command = validatedChange.getCommand(); |
| | | writeValue(command.getTargetType(), command.getTargetId(), command.getTargetKey(), validatedChange.getRequestedValue()); |
| | | validatedChange.accept(ChangeStatus.SUCCESS, null); |
| | | validatedChange.setAppliedValue(validatedChange.getRequestedValue()); |
| | | if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(command.getTargetType())) { |
| | | refreshConfigCache = true; |
| | | } |
| | | } |
| | | if (refreshConfigCache) { |
| | | configService.refreshSystemConfigCache(); |
| | | } |
| | | } |
| | | |
| | | private void writeValue(String targetType, String targetId, String targetKey, String value) { |
| | | AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType); |
| | | if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) { |
| | | if (!configService.saveConfigValue(targetKey, value)) { |
| | | throw new IllegalStateException("保存运行参数失败: " + targetKey); |
| | | } |
| | | return; |
| | | } |
| | | Integer parsedTargetId = parseTargetId(targetId, 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("保存站点参数失败: " + targetId + "/" + targetKey); |
| | | } |
| | | return; |
| | | } |
| | | if (AutoTuneTargetType.CRN.equals(parsedTargetType)) { |
| | | String column = "maxOutTask".equals(targetKey) ? "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("保存堆垛机参数失败: " + targetId + "/" + targetKey); |
| | | } |
| | | return; |
| | | } |
| | | String column = "maxOutTask".equals(targetKey) ? "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("保存双工位堆垛机参数失败: " + targetId + "/" + targetKey); |
| | | } |
| | | } |
| | | |
| | | private String readCurrentValue(String targetType, String targetId, String targetKey) { |
| | | AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType); |
| | | Integer parsedTargetId = parseTargetId(targetId, parsedTargetType); |
| | | if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) { |
| | | Config config = configService.getOne(new QueryWrapper<Config>().eq("code", targetKey).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(targetKey) ? crnp.getMaxOutTask() : crnp.getMaxInTask()); |
| | | } |
| | | BasDualCrnp dualCrnp = basDualCrnpService.getById(parsedTargetId); |
| | | if (dualCrnp == null) { |
| | | return null; |
| | | } |
| | | return toText("maxOutTask".equals(targetKey) ? 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<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 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()); |
| | | job.setStatus(rejectCount == 0 ? AutoTuneJobStatus.SUCCESS.getCode() : 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(); |
| | | } |
| | | 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(); |
| | | AutoTuneChangeCommand command = validatedChange.getCommand(); |
| | | change.setJobId(jobId); |
| | | change.setTargetType(command.getTargetType()); |
| | | change.setTargetId(normalizeTargetId(command.getTargetType(), command.getTargetId())); |
| | | change.setTargetKey(command.getTargetKey()); |
| | | change.setOldValue(validatedChange.getOldValue()); |
| | | change.setRequestedValue(validatedChange.getRequestedValue() == null ? command.getNewValue() : 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<AiAutoTuneJob> jobs = aiAutoTuneJobService.list( |
| | | new QueryWrapper<AiAutoTuneJob>() |
| | | .eq("status", AutoTuneJobStatus.SUCCESS.getCode()) |
| | | .ne("trigger_type", AutoTuneTriggerType.ROLLBACK.getCode()) |
| | | .orderByDesc("finish_time") |
| | | .last("limit 20") |
| | | ); |
| | | if (jobs == null || jobs.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | for (AiAutoTuneJob job : jobs) { |
| | | 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 AutoTuneApplyResult buildResult(AiAutoTuneJob job, List<AiAutoTuneChange> 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<ValidatedChange> validatedChanges) { |
| | | for (ValidatedChange validatedChange : validatedChanges) { |
| | | if (ChangeStatus.REJECTED.equals(validatedChange.getStatus())) { |
| | | continue; |
| | | } |
| | | if (ChangeStatus.NO_CHANGE.equals(validatedChange.getStatus())) { |
| | | continue; |
| | | } |
| | | validatedChange.reject("同批次存在被拒绝变更,未执行写入"); |
| | | } |
| | | } |
| | | |
| | | 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 String normalizeTargetId(String targetType, String targetId) { |
| | | AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType); |
| | | if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) { |
| | | return ""; |
| | | } |
| | | return targetId == null ? "" : targetId.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<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 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 ValidatedChange { |
| | | private final AutoTuneChangeCommand command; |
| | | 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; |
| | | } |
| | | |
| | | private ValidatedChange reject(String reason) { |
| | | this.status = ChangeStatus.REJECTED; |
| | | this.rejectReason = reason; |
| | | this.appliedValue = null; |
| | | return this; |
| | | } |
| | | |
| | | 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 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; |
| | | } |
| | | } |
| | | } |