训练任务加了限制(training=2,pending=5)

This commit is contained in:
wanxiaoli 2026-08-05 11:51:20 +08:00
parent 69f8b6a0e9
commit 2ee8b22ce2
5 changed files with 195 additions and 15 deletions

View File

@ -29,6 +29,12 @@ public class ModelTrainController {
@Autowired
private ObjectMapper objectMapper;
@PreAuthorize("hasAuthority('modelTrain:add')")
@GetMapping("/capacity/check")
public ResponseResult capacityCheck() {
return ResponseResult.successData(modelTrainService.checkCapacity());
}
/**
* 接收 Python 端的训练状态回调
*/

View File

@ -47,4 +47,6 @@ public interface ModelTrainService extends IService<ModelTrainTask> {
* @return 是否成功
*/
boolean publishModel(String taskId, String versionTag);
Map<String, Object> checkCapacity();
}

View File

@ -283,6 +283,7 @@ public class DeviceServiceImpl
continue;
}
List<String> sizeCols = getSizeColumnsByDeviceType(rowType);
String sizeJson = null;
if (hasSizeJson) {
String raw = cleanString(getString(row, idx.get("size")));
@ -292,10 +293,12 @@ public class DeviceServiceImpl
errors.add(err(r, "size 非法JSON"));
continue;
}
if (!validateSizeJsonPositive(sizeJson, sizeCols, errors, r)) {
continue;
}
}
}
if (sizeJson == null) {
List<String> sizeCols = getSizeColumnsByDeviceType(rowType);
if (sizeCols.isEmpty()) {
errors.add(err(r, "未知设备类型: " + rowType));
continue;
@ -303,28 +306,46 @@ public class DeviceServiceImpl
Map<String, Object> m = new LinkedHashMap<>();
for (String k : sizeCols) {
Integer i = idx.get(k);
if (i == null) continue;
Double v = getDoubleFlexible(row, i, evaluator, formatter);
if (v != null) {
m.put(k, v);
if (i == null) {
errors.add(err(r, "缺少尺寸列: " + k));
m.clear();
break;
}
Double v = getDoubleFlexible(row, i, evaluator, formatter);
if (v == null || v <= 0) {
errors.add(err(r, "尺寸 " + k + " 必须大于0"));
m.clear();
break;
}
m.put(k, v);
}
if (m.isEmpty()) {
errors.add(err(r, "尺寸列为空"));
continue;
}
sizeJson = objectMapper.writeValueAsString(m);
}
int errSizeBefore = errors.size();
Double volume = readPositiveOptional(row, idx.get("volume"),
evaluator, formatter, errors, r, "容量");
Double flowRate = readPositiveOptional(row, idx.get("flow_rate"),
evaluator, formatter, errors, r, "流量");
Double pulseVelocity =
readPositiveOptional(row, idx.get("pulse_velocity"),
evaluator, formatter, errors, r, "脉冲速度");
if (errors.size() > errSizeBefore) {
continue;
}
Device d = new Device();
d.setType(rowType);
d.setProjectId(projectId == null || projectId.isBlank() ? "-1" : projectId);
d.setCode(code);
d.setName(name);
d.setSize(sizeJson);
if (idx.containsKey("volume")) d.setVolume(getDoubleFlexible(row, idx.get("volume"), evaluator, formatter));
if (idx.containsKey("flow_rate")) d.setFlowRate(getDoubleFlexible(row, idx.get("flow_rate"), evaluator, formatter));
if (idx.containsKey("pulse_velocity")) d.setPulseVelocity(getDoubleFlexible(row, idx.get("pulse_velocity"), evaluator, formatter));
d.setVolume(volume);
d.setFlowRate(flowRate);
d.setPulseVelocity(pulseVelocity);
d.setCreatedAt(LocalDateTime.now());
d.setUpdatedAt(LocalDateTime.now());
d.setModifier(currentUsername());
@ -359,6 +380,103 @@ public class DeviceServiceImpl
}
}
private Double readPositiveOptional(Row row, Integer i,
FormulaEvaluator evaluator,
DataFormatter formatter,
List<Map<String, Object>> errors, int r,
String label) {
if (i == null) return null;
Cell c = row.getCell(i);
if (c == null) return null;
String s = formatter.formatCellValue(c, evaluator);
if (s == null) return null;
String t = s.trim();
if (t.isEmpty()) return null;
try {
double v = Double.parseDouble(t);
if (v <= 0) {
errors.add(err(r, label + " 必须大于0"));
return null;
}
return v;
} catch (Exception e) {
errors.add(err(r, label + " 必须为数字"));
return null;
}
}
private boolean validateSizeJsonPositive(String sizeJson,
List<String> schemaKeys,
List<Map<String, Object>> errors,
int r) {
try {
JsonNode node = objectMapper.readTree(sizeJson);
if (node == null || !node.isObject()) {
errors.add(err(r, "size 必须为JSON对象"));
return false;
}
if (schemaKeys != null && !schemaKeys.isEmpty()) {
for (String k : schemaKeys) {
JsonNode v = node.get(k);
if (v == null || v.isNull()) {
errors.add(err(r, "尺寸 " + k + " 不能为空"));
return false;
}
Double dv = parseJsonDouble(v);
if (dv == null) {
errors.add(err(r, "尺寸 " + k + " 必须为数字"));
return false;
}
if (dv <= 0) {
errors.add(err(r, "尺寸 " + k + " 必须大于0"));
return false;
}
}
}
var it = node.fields();
while (it.hasNext()) {
var e = it.next();
JsonNode v = e.getValue();
Double dv = parseJsonDouble(v);
if (dv == null) {
if (v != null && !v.isNull() && v.isTextual()) {
String t = v.asText();
if (t != null && !t.trim().isEmpty()) {
errors.add(err(r, "尺寸 " + e.getKey() + " 必须为数字"));
return false;
}
}
continue;
}
if (dv <= 0) {
errors.add(err(r, "尺寸 " + e.getKey() + " 必须大于0"));
return false;
}
}
return true;
} catch (Exception e) {
errors.add(err(r, "size 解析失败"));
return false;
}
}
private Double parseJsonDouble(JsonNode v) {
if (v == null || v.isNull()) return null;
if (v.isNumber()) return v.numberValue().doubleValue();
if (v.isTextual()) {
String t = v.asText();
if (t == null) return null;
String s = t.trim();
if (s.isEmpty()) return null;
try {
return Double.parseDouble(s);
} catch (Exception e) {
return null;
}
}
return null;
}
private String cleanString(String s) {
if (s == null) return null;
String t = s.trim();

View File

@ -76,6 +76,8 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
private static final Pattern VERSION_TAG_PATTERN = Pattern.compile("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$");
private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
private static final Pattern DERIVED_EXPR_ALLOWED_PATTERN = Pattern.compile("^[A-Za-z0-9_+\\-*/()\\s]+$");
private static final int MAX_TRAINING = 2;
private static final int MAX_PENDING = 5;
@Override
public String uploadDataset(MultipartFile file) {
@ -158,6 +160,7 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
@Override
@Transactional
public String submitTask(ModelTrainTask task) {
assertCapacityForSubmit();
// 1. 初始化状态
task.setStatus("Pending");
if (task.getTaskId() == null) {
@ -183,12 +186,55 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
return task.getTaskId();
}
@Override
public Map<String, Object> checkCapacity() {
long training = countByStatus(List.of("Training", "TRAINING"));
long pending = countByStatus(List.of("Pending", "PENDING"));
boolean canSubmit = !(training >= MAX_TRAINING && pending >= MAX_PENDING);
Map<String, Object> data = new HashMap<>();
data.put("canSubmit", canSubmit);
data.put("limits", Map.of(
"maxTraining", MAX_TRAINING,
"maxPending", MAX_PENDING
));
data.put("counts", Map.of(
"training", training,
"pending", pending
));
if (!canSubmit) {
data.put("blockReason", buildCapacityBlockReason(training, pending));
data.put("suggest", Map.of("retryAfterSeconds", 30));
}
return data;
}
private void assertCapacityForSubmit() {
long training = countByStatus(List.of("Training", "TRAINING"));
long pending = countByStatus(List.of("Pending", "PENDING"));
if (training >= MAX_TRAINING && pending >= MAX_PENDING) {
throw new BizException(buildCapacityBlockReason(training, pending));
}
}
private long countByStatus(List<String> statuses) {
QueryWrapper<ModelTrainTask> q = new QueryWrapper<>();
q.in("status", statuses);
return this.count(q);
}
private String buildCapacityBlockReason(long training, long pending) {
return "当前训练中(" + training + ")且等待中(" + pending + ")已达上限,请稍后再试";
}
@Async
public void asyncCallTrain(ModelTrainTask task) {
try {
// 更新状态为 Training
task.setStatus("Training");
this.updateById(task);
// task.setStatus("Training");
// this.updateById(task);
// 构建请求参数
Map<String, Object> request = new HashMap<>();
@ -557,6 +603,7 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
}
task.setStatus(status);
log.info("============================更新任务状态为:{} {}", taskId, status, callbackData);
if ("Success".equals(status)) {
String modelPathRaw = firstNonBlank(

View File

@ -145,9 +145,16 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
}
List<Object> max = this.listObjs(queryWrapper);
//判断查询是否存在 存在转换成int类型并给codeMax替换值
if (max.size() > 0) {
codeMax =
Integer.parseInt(max.get(0).toString().substring(max.get(0).toString().length() - 2));
if (max.size() > 0 && max.get(0) != null) {
String maxCode = max.get(0).toString();
if (maxCode.length() >= 2) {
String lastTwo = maxCode.substring(maxCode.length() - 2);
try {
codeMax = Integer.parseInt(lastTwo);
} catch (Exception e) {
codeMax = 0;
}
}
}
//2位数字编号
DecimalFormat df = new DecimalFormat("00");