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;
|
}
|
}
|
}
|