| | |
| | | private BasDualCrnpService basDualCrnpService; |
| | | @Autowired |
| | | private StationFlowCapacityService stationFlowCapacityService; |
| | | @Autowired(required = false) |
| | | @Autowired |
| | | private PlatformTransactionManager transactionManager; |
| | | |
| | | @Override |
| | |
| | | |
| | | private ValidatedChange validateChange(AutoTuneChangeCommand command, boolean dryRun, Date now) { |
| | | ValidatedChange validatedChange = new ValidatedChange(command); |
| | | AutoTuneRuleDefinition.Rule rule = AutoTuneRuleDefinition.findRule(command.getTargetType(), command.getTargetKey()); |
| | | AutoTuneRuleDefinition.Rule rule = AutoTuneRuleDefinition.findRule( |
| | | validatedChange.getTargetType(), |
| | | validatedChange.getTargetKey() |
| | | ); |
| | | if (rule == null) { |
| | | return validatedChange.reject("不支持的调参目标: " + command.getTargetType() + "/" + command.getTargetKey()); |
| | | return validatedChange.reject("不支持的调参目标: " |
| | | + validatedChange.getTargetType() + "/" + validatedChange.getTargetKey()); |
| | | } |
| | | validatedChange.setRule(rule); |
| | | |
| | | Integer requestedValue = parseRequestedInt(command.getNewValue()); |
| | | Integer requestedValue = parseRequestedInt(validatedChange.getRawRequestedValue()); |
| | | if (requestedValue == null) { |
| | | return validatedChange.reject(command.getTargetKey() + " 必须为整数"); |
| | | return validatedChange.reject(validatedChange.getTargetKey() + " 必须为整数"); |
| | | } |
| | | validatedChange.setRequestedIntValue(requestedValue); |
| | | validatedChange.setRequestedValue(String.valueOf(requestedValue)); |
| | | |
| | | CurrentValue currentValue = readCurrentValueForValidation(command, rule); |
| | | CurrentValue currentValue = readCurrentValueForValidation(validatedChange, rule); |
| | | if (currentValue.getRejectReason() != null) { |
| | | return validatedChange.reject(currentValue.getRejectReason()); |
| | | } |
| | | validatedChange.setOldValue(currentValue.getOldValue()); |
| | | |
| | | Integer maxValue = resolveMaxValue(command, rule, requestedValue); |
| | | Integer maxValue = resolveMaxValue(validatedChange, rule, requestedValue); |
| | | if (maxValue == null) { |
| | | return validatedChange.reject("站点 " + command.getTargetId() + " 缺少 OUT 方向 bufferCapacity,无法证明 outTaskLimit 上限"); |
| | | return validatedChange.reject("站点 " + validatedChange.getTargetId() |
| | | + " 缺少 OUT 方向 bufferCapacity,无法证明 outTaskLimit 上限"); |
| | | } |
| | | if (requestedValue < rule.getMinValue() || requestedValue > maxValue) { |
| | | return validatedChange.reject(command.getTargetKey() + " 必须在 " + rule.getMinValue() + "~" + maxValue + " 范围内"); |
| | | return validatedChange.reject(validatedChange.getTargetKey() + " 必须在 " |
| | | + rule.getMinValue() + "~" + maxValue + " 范围内"); |
| | | } |
| | | |
| | | if (Objects.equals(currentValue.getNumericValue(), requestedValue)) { |
| | |
| | | } |
| | | int step = Math.abs(requestedValue - currentValue.getNumericValue()); |
| | | if (step > rule.getMaxStep()) { |
| | | return validatedChange.reject(command.getTargetKey() + " 单次调整步长不能超过 " + rule.getMaxStep()); |
| | | return validatedChange.reject(validatedChange.getTargetKey() + " 单次调整步长不能超过 " + rule.getMaxStep()); |
| | | } |
| | | |
| | | Date cooldownExpireTime = findCooldownExpireTime(command, now); |
| | | Date cooldownExpireTime = findCooldownExpireTime(validatedChange, now); |
| | | if (cooldownExpireTime != null) { |
| | | validatedChange.setCooldownExpireTime(cooldownExpireTime); |
| | | return validatedChange.reject("目标仍在冷却期,冷却截止时间: " + cooldownExpireTime); |
| | |
| | | return validatedChange.accept(dryRun ? ChangeStatus.DRY_RUN : ChangeStatus.PENDING, null); |
| | | } |
| | | |
| | | private CurrentValue readCurrentValueForValidation(AutoTuneChangeCommand command, AutoTuneRuleDefinition.Rule rule) { |
| | | private CurrentValue readCurrentValueForValidation(ValidatedChange validatedChange, AutoTuneRuleDefinition.Rule rule) { |
| | | AutoTuneTargetType targetType = rule.getTargetType(); |
| | | Integer targetId = parseTargetId(command.getTargetId(), targetType); |
| | | Integer targetId = parseTargetId(validatedChange.getTargetId(), targetType); |
| | | if (!AutoTuneTargetType.SYS_CONFIG.equals(targetType) && targetId == null) { |
| | | return CurrentValue.rejected("targetId 必须为整数"); |
| | | } |
| | | if (AutoTuneTargetType.SYS_CONFIG.equals(targetType)) { |
| | | Config config = configService.getOne(new QueryWrapper<Config>().eq("code", command.getTargetKey()).last("limit 1")); |
| | | Config config = configService.getOne(new QueryWrapper<Config>().eq("code", validatedChange.getTargetKey()).last("limit 1")); |
| | | if (config == null) { |
| | | return CurrentValue.rejected("运行参数不存在: " + command.getTargetKey()); |
| | | return CurrentValue.rejected("运行参数不存在: " + validatedChange.getTargetKey()); |
| | | } |
| | | return numericCurrentValue(config.getValue(), false, command.getTargetKey()); |
| | | return numericCurrentValue(config.getValue(), false, validatedChange.getTargetKey()); |
| | | } |
| | | if (AutoTuneTargetType.STATION.equals(targetType)) { |
| | | BasStation station = basStationService.getById(targetId); |
| | | if (station == null) { |
| | | return CurrentValue.rejected("站点不存在: " + command.getTargetId()); |
| | | return CurrentValue.rejected("站点不存在: " + validatedChange.getTargetId()); |
| | | } |
| | | return numericCurrentValue(toText(station.getOutTaskLimit()), true, command.getTargetKey()); |
| | | return numericCurrentValue(toText(station.getOutTaskLimit()), true, validatedChange.getTargetKey()); |
| | | } |
| | | if (AutoTuneTargetType.CRN.equals(targetType)) { |
| | | BasCrnp crnp = basCrnpService.getById(targetId); |
| | | if (crnp == null) { |
| | | return CurrentValue.rejected("堆垛机不存在: " + command.getTargetId()); |
| | | return CurrentValue.rejected("堆垛机不存在: " + validatedChange.getTargetId()); |
| | | } |
| | | Integer value = "maxOutTask".equals(command.getTargetKey()) ? crnp.getMaxOutTask() : crnp.getMaxInTask(); |
| | | return numericCurrentValue(toText(value), false, command.getTargetKey()); |
| | | 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("双工位堆垛机不存在: " + command.getTargetId()); |
| | | return CurrentValue.rejected("双工位堆垛机不存在: " + validatedChange.getTargetId()); |
| | | } |
| | | Integer value = "maxOutTask".equals(command.getTargetKey()) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask(); |
| | | return numericCurrentValue(toText(value), false, command.getTargetKey()); |
| | | 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) { |
| | |
| | | } |
| | | } |
| | | |
| | | private Integer resolveMaxValue(AutoTuneChangeCommand command, |
| | | private Integer resolveMaxValue(ValidatedChange validatedChange, |
| | | AutoTuneRuleDefinition.Rule rule, |
| | | Integer requestedValue) { |
| | | if (!rule.isDynamicMaxValue()) { |
| | | return rule.getMaxValue(); |
| | | } |
| | | Integer targetId = parseTargetId(command.getTargetId(), rule.getTargetType()); |
| | | Integer targetId = parseTargetId(validatedChange.getTargetId(), rule.getTargetType()); |
| | | StationFlowCapacity capacity = stationFlowCapacityService.getOne( |
| | | new QueryWrapper<StationFlowCapacity>() |
| | | .eq("station_id", targetId) |
| | |
| | | return Math.max(0, capacity.getBufferCapacity()); |
| | | } |
| | | |
| | | private Date findCooldownExpireTime(AutoTuneChangeCommand command, Date now) { |
| | | String targetId = normalizeTargetId(command.getTargetType(), command.getTargetId()); |
| | | private Date findCooldownExpireTime(ValidatedChange validatedChange, Date now) { |
| | | List<AiAutoTuneChange> recentChanges = aiAutoTuneChangeService.list( |
| | | new QueryWrapper<AiAutoTuneChange>() |
| | | .eq("target_type", command.getTargetType()) |
| | | .eq("target_id", targetId) |
| | | .eq("target_key", command.getTargetKey()) |
| | | .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") |
| | |
| | | } |
| | | |
| | | private void applyValidatedChangesInTransaction(List<ValidatedChange> validatedChanges) { |
| | | if (transactionManager == null) { |
| | | applyValidatedChanges(validatedChanges); |
| | | return; |
| | | } |
| | | TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); |
| | | transactionTemplate.executeWithoutResult(status -> applyValidatedChanges(validatedChanges)); |
| | | } |
| | |
| | | if (ChangeStatus.NO_CHANGE.equals(validatedChange.getStatus())) { |
| | | continue; |
| | | } |
| | | AutoTuneChangeCommand command = validatedChange.getCommand(); |
| | | writeValue(command.getTargetType(), command.getTargetId(), command.getTargetKey(), validatedChange.getRequestedValue()); |
| | | writeValue( |
| | | validatedChange.getTargetType(), |
| | | validatedChange.getTargetId(), |
| | | validatedChange.getTargetKey(), |
| | | validatedChange.getRequestedValue() |
| | | ); |
| | | validatedChange.accept(ChangeStatus.SUCCESS, null); |
| | | validatedChange.setAppliedValue(validatedChange.getRequestedValue()); |
| | | if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(command.getTargetType())) { |
| | | if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(validatedChange.getTargetType())) { |
| | | refreshConfigCache = true; |
| | | } |
| | | } |
| | |
| | | private List<AiAutoTuneChange> rollbackChangesInTransaction(Long rollbackJobId, |
| | | List<AiAutoTuneChange> sourceChanges, |
| | | Date now) { |
| | | if (transactionManager == null) { |
| | | return rollbackChanges(rollbackJobId, sourceChanges, now); |
| | | } |
| | | TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); |
| | | return transactionTemplate.execute(status -> rollbackChanges(rollbackJobId, sourceChanges, now)); |
| | | } |
| | |
| | | } |
| | | |
| | | private void writeValue(String targetType, String targetId, String targetKey, String value) { |
| | | AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType); |
| | | 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(targetKey, value)) { |
| | | throw new IllegalStateException("保存运行参数失败: " + targetKey); |
| | | if (!configService.saveConfigValue(normalizedTargetKey, value)) { |
| | | throw new IllegalStateException("保存运行参数失败: " + normalizedTargetKey); |
| | | } |
| | | return; |
| | | } |
| | | Integer parsedTargetId = parseTargetId(targetId, parsedTargetType); |
| | | Integer parsedTargetId = parseTargetId(normalizedTargetId, parsedTargetType); |
| | | Integer intValue = value == null ? null : Integer.valueOf(value); |
| | | if (AutoTuneTargetType.STATION.equals(parsedTargetType)) { |
| | | boolean updated = basStationService.update(new UpdateWrapper<BasStation>() |
| | | .eq("station_id", parsedTargetId) |
| | | .set("out_task_limit", intValue)); |
| | | if (!updated) { |
| | | throw new IllegalStateException("保存站点参数失败: " + targetId + "/" + targetKey); |
| | | throw new IllegalStateException("保存站点参数失败: " + normalizedTargetId + "/" + normalizedTargetKey); |
| | | } |
| | | return; |
| | | } |
| | | if (AutoTuneTargetType.CRN.equals(parsedTargetType)) { |
| | | String column = "maxOutTask".equals(targetKey) ? "max_out_task" : "max_in_task"; |
| | | String column = "maxOutTask".equals(normalizedTargetKey) ? "max_out_task" : "max_in_task"; |
| | | boolean updated = basCrnpService.update(new UpdateWrapper<BasCrnp>() |
| | | .eq("crn_no", parsedTargetId) |
| | | .set(column, intValue)); |
| | | if (!updated) { |
| | | throw new IllegalStateException("保存堆垛机参数失败: " + targetId + "/" + targetKey); |
| | | throw new IllegalStateException("保存堆垛机参数失败: " + normalizedTargetId + "/" + normalizedTargetKey); |
| | | } |
| | | return; |
| | | } |
| | | String column = "maxOutTask".equals(targetKey) ? "max_out_task" : "max_in_task"; |
| | | String column = "maxOutTask".equals(normalizedTargetKey) ? "max_out_task" : "max_in_task"; |
| | | boolean updated = basDualCrnpService.update(new UpdateWrapper<BasDualCrnp>() |
| | | .eq("crn_no", parsedTargetId) |
| | | .set(column, intValue)); |
| | | if (!updated) { |
| | | throw new IllegalStateException("保存双工位堆垛机参数失败: " + targetId + "/" + targetKey); |
| | | throw new IllegalStateException("保存双工位堆垛机参数失败: " + normalizedTargetId + "/" + normalizedTargetKey); |
| | | } |
| | | } |
| | | |
| | | private String readCurrentValue(String targetType, String targetId, String targetKey) { |
| | | AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType); |
| | | Integer parsedTargetId = parseTargetId(targetId, parsedTargetType); |
| | | String normalizedTargetType = normalizeText(targetType); |
| | | String normalizedTargetKey = normalizeText(targetKey); |
| | | String normalizedTargetId = normalizeTargetId(normalizedTargetType, targetId); |
| | | AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(normalizedTargetType); |
| | | if (parsedTargetType == null) { |
| | | throw new IllegalArgumentException("不支持的调参目标: " + normalizedTargetType + "/" + normalizedTargetKey); |
| | | } |
| | | Integer parsedTargetId = parseTargetId(normalizedTargetId, parsedTargetType); |
| | | if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) { |
| | | Config config = configService.getOne(new QueryWrapper<Config>().eq("code", targetKey).last("limit 1")); |
| | | Config config = configService.getOne(new QueryWrapper<Config>().eq("code", normalizedTargetKey).last("limit 1")); |
| | | return config == null ? null : config.getValue(); |
| | | } |
| | | if (AutoTuneTargetType.STATION.equals(parsedTargetType)) { |
| | |
| | | if (crnp == null) { |
| | | return null; |
| | | } |
| | | return toText("maxOutTask".equals(targetKey) ? crnp.getMaxOutTask() : crnp.getMaxInTask()); |
| | | return toText("maxOutTask".equals(normalizedTargetKey) ? crnp.getMaxOutTask() : crnp.getMaxInTask()); |
| | | } |
| | | BasDualCrnp dualCrnp = basDualCrnpService.getById(parsedTargetId); |
| | | if (dualCrnp == null) { |
| | | return null; |
| | | } |
| | | return toText("maxOutTask".equals(targetKey) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask()); |
| | | return toText("maxOutTask".equals(normalizedTargetKey) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask()); |
| | | } |
| | | |
| | | private AiAutoTuneJob createJob(AutoTuneApplyRequest request, boolean dryRun, Date now) { |
| | |
| | | job.setSuccessCount(successCount); |
| | | job.setRejectCount(rejectCount); |
| | | job.setIntervalAfter(readIntervalMinutes()); |
| | | job.setStatus(rejectCount == 0 ? AutoTuneJobStatus.SUCCESS.getCode() : AutoTuneJobStatus.PARTIAL_SUCCESS.getCode()); |
| | | 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)); |
| | |
| | | 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.setTargetType(validatedChange.getTargetType()); |
| | | change.setTargetId(validatedChange.getTargetId()); |
| | | change.setTargetKey(validatedChange.getTargetKey()); |
| | | change.setOldValue(validatedChange.getOldValue()); |
| | | change.setRequestedValue(validatedChange.getRequestedValue() == null ? command.getNewValue() : validatedChange.getRequestedValue()); |
| | | change.setRequestedValue(validatedChange.getRequestedValue() == null |
| | | ? validatedChange.getRawRequestedValue() |
| | | : validatedChange.getRequestedValue()); |
| | | change.setAppliedValue(validatedChange.getAppliedValue()); |
| | | change.setResultStatus(validatedChange.getStatus().getCode()); |
| | | change.setRejectReason(validatedChange.getRejectReason()); |
| | |
| | | } |
| | | } |
| | | |
| | | private String normalizeTargetId(String targetType, String targetId) { |
| | | private static String normalizeTargetId(String targetType, String targetId) { |
| | | AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType); |
| | | if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) { |
| | | return ""; |
| | | } |
| | | return targetId == null ? "" : targetId.trim(); |
| | | return normalizeText(targetId); |
| | | } |
| | | |
| | | private static String normalizeText(String value) { |
| | | return value == null ? "" : value.trim(); |
| | | } |
| | | |
| | | private int readIntervalMinutes() { |
| | |
| | | |
| | | 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 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) { |
| | |
| | | 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; |
| | | } |