训练任务加了限制(training=2,pending=5)
This commit is contained in:
parent
69f8b6a0e9
commit
2ee8b22ce2
@ -29,6 +29,12 @@ public class ModelTrainController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('modelTrain:add')")
|
||||||
|
@GetMapping("/capacity/check")
|
||||||
|
public ResponseResult capacityCheck() {
|
||||||
|
return ResponseResult.successData(modelTrainService.checkCapacity());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接收 Python 端的训练状态回调
|
* 接收 Python 端的训练状态回调
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -47,4 +47,6 @@ public interface ModelTrainService extends IService<ModelTrainTask> {
|
|||||||
* @return 是否成功
|
* @return 是否成功
|
||||||
*/
|
*/
|
||||||
boolean publishModel(String taskId, String versionTag);
|
boolean publishModel(String taskId, String versionTag);
|
||||||
|
|
||||||
|
Map<String, Object> checkCapacity();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -283,6 +283,7 @@ public class DeviceServiceImpl
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> sizeCols = getSizeColumnsByDeviceType(rowType);
|
||||||
String sizeJson = null;
|
String sizeJson = null;
|
||||||
if (hasSizeJson) {
|
if (hasSizeJson) {
|
||||||
String raw = cleanString(getString(row, idx.get("size")));
|
String raw = cleanString(getString(row, idx.get("size")));
|
||||||
@ -292,10 +293,12 @@ public class DeviceServiceImpl
|
|||||||
errors.add(err(r, "size 非法JSON"));
|
errors.add(err(r, "size 非法JSON"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!validateSizeJsonPositive(sizeJson, sizeCols, errors, r)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sizeJson == null) {
|
if (sizeJson == null) {
|
||||||
List<String> sizeCols = getSizeColumnsByDeviceType(rowType);
|
|
||||||
if (sizeCols.isEmpty()) {
|
if (sizeCols.isEmpty()) {
|
||||||
errors.add(err(r, "未知设备类型: " + rowType));
|
errors.add(err(r, "未知设备类型: " + rowType));
|
||||||
continue;
|
continue;
|
||||||
@ -303,28 +306,46 @@ public class DeviceServiceImpl
|
|||||||
Map<String, Object> m = new LinkedHashMap<>();
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
for (String k : sizeCols) {
|
for (String k : sizeCols) {
|
||||||
Integer i = idx.get(k);
|
Integer i = idx.get(k);
|
||||||
if (i == null) continue;
|
if (i == null) {
|
||||||
Double v = getDoubleFlexible(row, i, evaluator, formatter);
|
errors.add(err(r, "缺少尺寸列: " + k));
|
||||||
if (v != null) {
|
m.clear();
|
||||||
m.put(k, v);
|
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()) {
|
if (m.isEmpty()) {
|
||||||
errors.add(err(r, "尺寸列为空"));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sizeJson = objectMapper.writeValueAsString(m);
|
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();
|
Device d = new Device();
|
||||||
d.setType(rowType);
|
d.setType(rowType);
|
||||||
d.setProjectId(projectId == null || projectId.isBlank() ? "-1" : projectId);
|
d.setProjectId(projectId == null || projectId.isBlank() ? "-1" : projectId);
|
||||||
d.setCode(code);
|
d.setCode(code);
|
||||||
d.setName(name);
|
d.setName(name);
|
||||||
d.setSize(sizeJson);
|
d.setSize(sizeJson);
|
||||||
if (idx.containsKey("volume")) d.setVolume(getDoubleFlexible(row, idx.get("volume"), evaluator, formatter));
|
d.setVolume(volume);
|
||||||
if (idx.containsKey("flow_rate")) d.setFlowRate(getDoubleFlexible(row, idx.get("flow_rate"), evaluator, formatter));
|
d.setFlowRate(flowRate);
|
||||||
if (idx.containsKey("pulse_velocity")) d.setPulseVelocity(getDoubleFlexible(row, idx.get("pulse_velocity"), evaluator, formatter));
|
d.setPulseVelocity(pulseVelocity);
|
||||||
d.setCreatedAt(LocalDateTime.now());
|
d.setCreatedAt(LocalDateTime.now());
|
||||||
d.setUpdatedAt(LocalDateTime.now());
|
d.setUpdatedAt(LocalDateTime.now());
|
||||||
d.setModifier(currentUsername());
|
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) {
|
private String cleanString(String s) {
|
||||||
if (s == null) return null;
|
if (s == null) return null;
|
||||||
String t = s.trim();
|
String t = s.trim();
|
||||||
|
|||||||
@ -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 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 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 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
|
@Override
|
||||||
public String uploadDataset(MultipartFile file) {
|
public String uploadDataset(MultipartFile file) {
|
||||||
@ -158,6 +160,7 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public String submitTask(ModelTrainTask task) {
|
public String submitTask(ModelTrainTask task) {
|
||||||
|
assertCapacityForSubmit();
|
||||||
// 1. 初始化状态
|
// 1. 初始化状态
|
||||||
task.setStatus("Pending");
|
task.setStatus("Pending");
|
||||||
if (task.getTaskId() == null) {
|
if (task.getTaskId() == null) {
|
||||||
@ -182,13 +185,56 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
|
|||||||
|
|
||||||
return task.getTaskId();
|
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
|
@Async
|
||||||
public void asyncCallTrain(ModelTrainTask task) {
|
public void asyncCallTrain(ModelTrainTask task) {
|
||||||
try {
|
try {
|
||||||
// 更新状态为 Training
|
// 更新状态为 Training
|
||||||
task.setStatus("Training");
|
// task.setStatus("Training");
|
||||||
this.updateById(task);
|
// this.updateById(task);
|
||||||
|
|
||||||
// 构建请求参数
|
// 构建请求参数
|
||||||
Map<String, Object> request = new HashMap<>();
|
Map<String, Object> request = new HashMap<>();
|
||||||
@ -557,7 +603,8 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
task.setStatus(status);
|
task.setStatus(status);
|
||||||
|
log.info("============================更新任务状态为:{} {}", taskId, status, callbackData);
|
||||||
|
|
||||||
if ("Success".equals(status)) {
|
if ("Success".equals(status)) {
|
||||||
String modelPathRaw = firstNonBlank(
|
String modelPathRaw = firstNonBlank(
|
||||||
(String) callbackData.get("model_path"),
|
(String) callbackData.get("model_path"),
|
||||||
|
|||||||
@ -145,9 +145,16 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
|
|||||||
}
|
}
|
||||||
List<Object> max = this.listObjs(queryWrapper);
|
List<Object> max = this.listObjs(queryWrapper);
|
||||||
//判断查询是否存在 存在转换成int类型并给codeMax替换值
|
//判断查询是否存在 存在转换成int类型并给codeMax替换值
|
||||||
if (max.size() > 0) {
|
if (max.size() > 0 && max.get(0) != null) {
|
||||||
codeMax =
|
String maxCode = max.get(0).toString();
|
||||||
Integer.parseInt(max.get(0).toString().substring(max.get(0).toString().length() - 2));
|
if (maxCode.length() >= 2) {
|
||||||
|
String lastTwo = maxCode.substring(maxCode.length() - 2);
|
||||||
|
try {
|
||||||
|
codeMax = Integer.parseInt(lastTwo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
codeMax = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//2位数字编号
|
//2位数字编号
|
||||||
DecimalFormat df = new DecimalFormat("00");
|
DecimalFormat df = new DecimalFormat("00");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user