后端适配新数据集及设备类型
This commit is contained in:
parent
e1f770c795
commit
c83b627674
@ -114,12 +114,11 @@ public class SimBuilder {
|
||||
List<Material> mats = materialService.list(new QueryWrapper<Material>().in("material_id", mids));
|
||||
for (Material m : mats) {
|
||||
String type = "unknown";
|
||||
// 简单推导规则: 优先判定 Pu,其次 U
|
||||
if (m.getPuConcentration() != null && m.getPuConcentration().doubleValue() > 0) {
|
||||
type = "Pu";
|
||||
} else if (m.getUConcentration() != null && m.getUConcentration().doubleValue() > 0) {
|
||||
type = "U";
|
||||
}
|
||||
boolean hasU = m.getUConcentration() != null && m.getUConcentration().doubleValue() > 0;
|
||||
boolean hasPu = m.getPuConcentration() != null && m.getPuConcentration().doubleValue() > 0;
|
||||
if (hasU && hasPu) type = "Mixed";
|
||||
else if (hasPu) type = "Pu";
|
||||
else if (hasU) type = "U";
|
||||
out.put(m.getMaterialId(), type);
|
||||
}
|
||||
return out;
|
||||
@ -151,27 +150,71 @@ public class SimBuilder {
|
||||
case "TubeBundleTank"://管束槽
|
||||
parseCommonSize(sizeNode, staticProps);
|
||||
break;
|
||||
case "PulsedCylindricalColumn":
|
||||
if (sizeNode.has("upper_inner_diameter")) staticProps.put("upper_inner_diameter", sizeNode.get("upper_inner_diameter").asDouble());
|
||||
if (sizeNode.has("upper_outer_diameter")) staticProps.put("upper_outer_diameter", sizeNode.get("upper_outer_diameter").asDouble());
|
||||
if (sizeNode.has("upper_height")) staticProps.put("upper_height", sizeNode.get("upper_height").asDouble());
|
||||
if (sizeNode.has("tray_diameter")) staticProps.put("tray_diameter", sizeNode.get("tray_diameter").asDouble());
|
||||
if (sizeNode.has("tray_height")) staticProps.put("tray_height", sizeNode.get("tray_height").asDouble());
|
||||
if (sizeNode.has("lower_inner_diameter")) staticProps.put("lower_inner_diameter", sizeNode.get("lower_inner_diameter").asDouble());
|
||||
if (sizeNode.has("lower_outer_diameter")) staticProps.put("lower_outer_diameter", sizeNode.get("lower_outer_diameter").asDouble());
|
||||
if (sizeNode.has("lower_height")) staticProps.put("lower_height", sizeNode.get("lower_height").asDouble());
|
||||
// if (staticProps.containsKey("tray_diameter")) staticProps.put("diameter", staticProps.get("tray_diameter"));
|
||||
// if (staticProps.containsKey("tray_height")) staticProps.put("height", staticProps.get("tray_height"));
|
||||
break;
|
||||
case "PulsedAnnularColumn":
|
||||
if (sizeNode.has("upper_inner_diameter")) staticProps.put("upper_inner_diameter", sizeNode.get("upper_inner_diameter").asDouble());
|
||||
if (sizeNode.has("upper_outer_diameter")) staticProps.put("upper_outer_diameter", sizeNode.get("upper_outer_diameter").asDouble());
|
||||
if (sizeNode.has("upper_height")) staticProps.put("upper_height", sizeNode.get("upper_height").asDouble());
|
||||
if (sizeNode.has("tray_inner_diameter")) staticProps.put("tray_inner_diameter", sizeNode.get("tray_inner_diameter").asDouble());
|
||||
if (sizeNode.has("tray_outer_diameter")) staticProps.put("tray_outer_diameter", sizeNode.get("tray_outer_diameter").asDouble());
|
||||
if (sizeNode.has("tray_height")) staticProps.put("tray_height", sizeNode.get("tray_height").asDouble());
|
||||
if (sizeNode.has("lower_inner_diameter")) staticProps.put("lower_inner_diameter", sizeNode.get("lower_inner_diameter").asDouble());
|
||||
if (sizeNode.has("lower_outer_diameter")) staticProps.put("lower_outer_diameter", sizeNode.get("lower_outer_diameter").asDouble());
|
||||
if (sizeNode.has("lower_height")) staticProps.put("lower_height", sizeNode.get("lower_height").asDouble());
|
||||
// if (staticProps.containsKey("tray_outer_diameter")) staticProps.put("diameter", staticProps.get("tray_outer_diameter"));
|
||||
// if (staticProps.containsKey("tray_height")) staticProps.put("height", staticProps.get("tray_height"));
|
||||
break;
|
||||
|
||||
case "ExtractionColumn":
|
||||
// 优先提取 tray_section (塔身)
|
||||
if (sizeNode.has("tray_section")) {
|
||||
parseCommonSize(sizeNode.get("tray_section"), staticProps);
|
||||
}
|
||||
// 可选:将其他段的尺寸作为特殊属性注入,例如 lower_expanded_height
|
||||
if (sizeNode.has("tray_diameter")) staticProps.put("tray_diameter", sizeNode.get("tray_diameter").asDouble());
|
||||
if (sizeNode.has("tray_height")) staticProps.put("tray_height", sizeNode.get("tray_height").asDouble());
|
||||
if (sizeNode.has("upper_diameter")) staticProps.put("upper_diameter", sizeNode.get("upper_diameter").asDouble());
|
||||
if (sizeNode.has("upper_height")) staticProps.put("upper_height", sizeNode.get("upper_height").asDouble());
|
||||
if (sizeNode.has("lower_diameter")) staticProps.put("lower_diameter", sizeNode.get("lower_diameter").asDouble());
|
||||
if (sizeNode.has("lower_height")) staticProps.put("lower_height", sizeNode.get("lower_height").asDouble());
|
||||
// if (staticProps.containsKey("tray_diameter")) staticProps.put("diameter", staticProps.get("tray_diameter"));
|
||||
// if (staticProps.containsKey("tray_height")) staticProps.put("height", staticProps.get("tray_height"));
|
||||
// if (!staticProps.containsKey("diameter") && sizeNode.has("tray_section")) {
|
||||
// parseCommonSize(sizeNode.get("tray_section"), staticProps);
|
||||
// }
|
||||
break;
|
||||
|
||||
case "FluidizedBed":
|
||||
// 优先提取 reaction_section (反应段)
|
||||
if (sizeNode.has("reaction_section")) {
|
||||
parseCommonSize(sizeNode.get("reaction_section"), staticProps);
|
||||
}
|
||||
if (sizeNode.has("reaction_diameter")) staticProps.put("reaction_diameter", sizeNode.get("reaction_diameter").asDouble());
|
||||
if (sizeNode.has("reaction_height")) staticProps.put("reaction_height", sizeNode.get("reaction_height").asDouble());
|
||||
if (sizeNode.has("expanded_diameter")) staticProps.put("expanded_diameter", sizeNode.get("expanded_diameter").asDouble());
|
||||
if (sizeNode.has("expanded_height")) staticProps.put("expanded_height", sizeNode.get("expanded_height").asDouble());
|
||||
if (sizeNode.has("transition_height")) staticProps.put("transition_height", sizeNode.get("transition_height").asDouble());
|
||||
// if (staticProps.containsKey("reaction_diameter")) staticProps.put("diameter", staticProps.get("reaction_diameter"));
|
||||
// if (staticProps.containsKey("reaction_height")) staticProps.put("height", staticProps.get("reaction_height"));
|
||||
// if (!staticProps.containsKey("diameter") && sizeNode.has("reaction_section")) {
|
||||
// parseCommonSize(sizeNode.get("reaction_section"), staticProps);
|
||||
// }
|
||||
break;
|
||||
|
||||
case "ACFTank":
|
||||
// 优先提取 annular_cylinder (圆柱段)
|
||||
if (sizeNode.has("annular_cylinder")) {
|
||||
parseCommonSize(sizeNode.get("annular_cylinder"), staticProps);
|
||||
}
|
||||
if (sizeNode.has("cylinder_diameter")) staticProps.put("cylinder_diameter", sizeNode.get("cylinder_diameter").asDouble());
|
||||
if (sizeNode.has("cylinder_height")) staticProps.put("cylinder_height", sizeNode.get("cylinder_height").asDouble());
|
||||
if (sizeNode.has("bottom_diameter")) staticProps.put("bottom_diameter", sizeNode.get("bottom_diameter").asDouble());
|
||||
if (sizeNode.has("bottom_height")) staticProps.put("bottom_height", sizeNode.get("bottom_height").asDouble());
|
||||
if (sizeNode.has("scab_thickness")) staticProps.put("scab_thickness", sizeNode.get("scab_thickness").asDouble());
|
||||
if (sizeNode.has("scab_height")) staticProps.put("scab_height", sizeNode.get("scab_height").asDouble());
|
||||
// if (staticProps.containsKey("cylinder_diameter")) staticProps.put("diameter", staticProps.get("cylinder_diameter"));
|
||||
// if (staticProps.containsKey("cylinder_height")) staticProps.put("height", staticProps.get("cylinder_height"));
|
||||
// if (!staticProps.containsKey("diameter") && sizeNode.has("annular_cylinder")) {
|
||||
// parseCommonSize(sizeNode.get("annular_cylinder"), staticProps);
|
||||
// }
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -364,6 +407,11 @@ public class SimBuilder {
|
||||
if (m.getPuConcentration() != null) s.put("pu_concentration", m.getPuConcentration().doubleValue());
|
||||
if (m.getUEnrichment() != null) s.put("u_enrichment", m.getUEnrichment().doubleValue());
|
||||
if (m.getPuIsotope() != null) s.put("pu_isotope", m.getPuIsotope().doubleValue());
|
||||
if (m.getEPu240() != null) s.put("e_pu240", m.getEPu240().doubleValue());
|
||||
if (m.getEPu242() != null) s.put("e_pu242", m.getEPu242().doubleValue());
|
||||
if (m.getEPu241() != null) s.put("e_pu241", m.getEPu241().doubleValue());
|
||||
if (m.getEPu239() != null) s.put("e_pu239", m.getEPu239().doubleValue());
|
||||
if (m.getEPu238() != null) s.put("e_pu238", m.getEPu238().doubleValue());
|
||||
if (m.getUo2Density() != null) s.put("uo2_density", m.getUo2Density().doubleValue());
|
||||
if (m.getPuo2Density() != null) s.put("puo2_density", m.getPuo2Density().doubleValue());
|
||||
if (m.getHno3Acidity() != null) s.put("hno3_acidity", m.getHno3Acidity().doubleValue());
|
||||
|
||||
@ -103,7 +103,14 @@ public class AlgorithmModelController {
|
||||
QueryWrapper<AlgorithmModel> qw = new QueryWrapper<>();
|
||||
if (algorithmType != null && !algorithmType.isEmpty()) qw.eq("algorithm_type", algorithmType);
|
||||
if (deviceType != null && !deviceType.isEmpty()) qw.eq("device_type", deviceType);
|
||||
if (materialType != null && !materialType.isEmpty()) qw.eq("material_type", materialType);
|
||||
String mt = normalizeMaterialType(materialType);
|
||||
if (mt != null) {
|
||||
if ("Mixed".equals(mt)) {
|
||||
qw.in("material_type", List.of("Mixed", "MIX"));
|
||||
} else {
|
||||
qw.eq("material_type", mt);
|
||||
}
|
||||
}
|
||||
if (versionTag != null && !versionTag.isEmpty()) qw.eq("version_tag", versionTag);
|
||||
if (isCurrent != null && !isCurrent.isEmpty()) qw.eq("is_current", isCurrent);
|
||||
qw.orderByDesc("updated_at");
|
||||
@ -111,6 +118,46 @@ public class AlgorithmModelController {
|
||||
return algorithmModelService.page(page, qw);
|
||||
}
|
||||
|
||||
@GetMapping("/options")
|
||||
@Operation(summary = "获取模型版本选项列表", description = "用于界面下拉选择:按算法类型与设备类型返回模型版本列表(value=模型ID,label=版本号),并返回当前激活模型ID")
|
||||
public Map<String, Object> options(@RequestParam String algorithmType,
|
||||
@RequestParam String deviceType,
|
||||
@RequestParam(required = false) String materialType) {
|
||||
QueryWrapper<AlgorithmModel> qw = new QueryWrapper<>();
|
||||
qw.eq("algorithm_type", algorithmType);
|
||||
qw.eq("device_type", deviceType);
|
||||
String mt = normalizeMaterialType(materialType);
|
||||
if (mt != null) {
|
||||
if ("Mixed".equals(mt)) {
|
||||
qw.in("material_type", List.of("Mixed", "MIX"));
|
||||
} else {
|
||||
qw.eq("material_type", mt);
|
||||
}
|
||||
}
|
||||
qw.orderByDesc("updated_at");
|
||||
List<AlgorithmModel> list = algorithmModelService.list(qw);
|
||||
|
||||
String currentModelId = null;
|
||||
List<Map<String, Object>> options = new java.util.ArrayList<>();
|
||||
for (AlgorithmModel m : list) {
|
||||
if (m == null) continue;
|
||||
if (currentModelId == null && m.getIsCurrent() != null && m.getIsCurrent() == 1) {
|
||||
currentModelId = m.getAlgorithmModelId();
|
||||
}
|
||||
Map<String, Object> opt = new HashMap<>();
|
||||
opt.put("value", m.getAlgorithmModelId());
|
||||
opt.put("label", m.getVersionTag());
|
||||
opt.put("isCurrent", m.getIsCurrent());
|
||||
opt.put("materialType", m.getMaterialType());
|
||||
opt.put("updatedAt", m.getUpdatedAt());
|
||||
options.add(opt);
|
||||
}
|
||||
Map<String, Object> out = new HashMap<>();
|
||||
out.put("currentModelId", currentModelId);
|
||||
out.put("options", options);
|
||||
return out;
|
||||
}
|
||||
|
||||
//返回:该算法+设备类型+材料类型的当前激活版本
|
||||
@GetMapping("/current")
|
||||
@Operation(summary = "获取当前激活版本", description = "根据算法类型、设备类型与材料类型,返回 is_current=1 的模型版本")
|
||||
@ -120,8 +167,13 @@ public class AlgorithmModelController {
|
||||
QueryWrapper<AlgorithmModel> qw = new QueryWrapper<>();
|
||||
qw.eq("algorithm_type", algorithmType);
|
||||
qw.eq("device_type", deviceType);
|
||||
if (materialType != null && !materialType.isEmpty()) {
|
||||
qw.eq("material_type", materialType);
|
||||
String mt = normalizeMaterialType(materialType);
|
||||
if (mt != null) {
|
||||
if ("Mixed".equals(mt)) {
|
||||
qw.in("material_type", List.of("Mixed", "MIX"));
|
||||
} else {
|
||||
qw.eq("material_type", mt);
|
||||
}
|
||||
}
|
||||
qw.eq("is_current", 1);
|
||||
qw.orderByDesc("updated_at");
|
||||
@ -129,7 +181,7 @@ public class AlgorithmModelController {
|
||||
}
|
||||
|
||||
@Log(value = "激活模型版本", module = "算法模型管理")
|
||||
@PreAuthorize("hasAuthority('algorithmModel:activate')")
|
||||
// @PreAuthorize("hasAuthority('algorithmModel:activate')")
|
||||
@PostMapping("/activate")
|
||||
@Operation(summary = "激活模型版本", description = "将目标模型版本设为当前,并将同组(算法+设备+材料)其他版本设为非当前")
|
||||
public boolean activate(@RequestParam String algorithmModelId) {
|
||||
@ -139,8 +191,13 @@ public class AlgorithmModelController {
|
||||
QueryWrapper<AlgorithmModel> qw = new QueryWrapper<>();
|
||||
qw.eq("algorithm_type", model.getAlgorithmType());
|
||||
qw.eq("device_type", model.getDeviceType());
|
||||
if (model.getMaterialType() != null && !model.getMaterialType().isEmpty()) {
|
||||
qw.eq("material_type", model.getMaterialType());
|
||||
String mt = normalizeMaterialType(model.getMaterialType());
|
||||
if (mt != null) {
|
||||
if ("Mixed".equals(mt)) {
|
||||
qw.in("material_type", List.of("Mixed", "MIX"));
|
||||
} else {
|
||||
qw.eq("material_type", mt);
|
||||
}
|
||||
} else {
|
||||
qw.and(wrapper -> wrapper.isNull("material_type").or().eq("material_type", ""));
|
||||
}
|
||||
@ -149,6 +206,7 @@ public class AlgorithmModelController {
|
||||
algorithmModelService.update(upd, qw);
|
||||
// 设置当前版本
|
||||
model.setIsCurrent(1);
|
||||
model.setMaterialType(mt);
|
||||
model.setModifier(currentUsername());
|
||||
model.setUpdatedAt(LocalDateTime.now());
|
||||
return algorithmModelService.updateById(model);
|
||||
@ -161,7 +219,7 @@ public class AlgorithmModelController {
|
||||
public Map<String, Object> trainExcel(@RequestBody Map<String, Object> body) {
|
||||
String algorithmType = str(body.get("algorithm_type"));
|
||||
String deviceType = str(body.get("device_type"));
|
||||
String materialType = str(body.getOrDefault("material_type", ""));
|
||||
String materialType = normalizeMaterialType(str(body.getOrDefault("material_type", "")));
|
||||
String datasetPath = str(body.get("dataset_path"));
|
||||
String modelDir = str(body.getOrDefault("model_dir", ""));
|
||||
boolean activate = bool(body.getOrDefault("activate", false));
|
||||
@ -216,7 +274,11 @@ public class AlgorithmModelController {
|
||||
QueryWrapper<AlgorithmModel> qw = new QueryWrapper<>();
|
||||
qw.eq("algorithm_type", algorithmType).eq("device_type", deviceType);
|
||||
if (!isBlank(materialType)) {
|
||||
if ("Mixed".equals(materialType)) {
|
||||
qw.in("material_type", List.of("Mixed", "MIX"));
|
||||
} else {
|
||||
qw.eq("material_type", materialType);
|
||||
}
|
||||
} else {
|
||||
qw.and(wrapper -> wrapper.isNull("material_type").or().eq("material_type", ""));
|
||||
}
|
||||
@ -235,7 +297,7 @@ public class AlgorithmModelController {
|
||||
public Map<String, Object> trainSamples(@RequestBody Map<String, Object> body) {
|
||||
String algorithmType = str(body.get("algorithm_type"));
|
||||
String deviceType = str(body.get("device_type"));
|
||||
String materialType = str(body.getOrDefault("material_type", ""));
|
||||
String materialType = normalizeMaterialType(str(body.getOrDefault("material_type", "")));
|
||||
Object samples = body.get("samples"); // 期望为 List<Map>,由前端提供
|
||||
String modelDir = str(body.getOrDefault("model_dir", ""));
|
||||
boolean activate = bool(body.getOrDefault("activate", false));
|
||||
@ -276,7 +338,11 @@ public class AlgorithmModelController {
|
||||
QueryWrapper<AlgorithmModel> qw = new QueryWrapper<>();
|
||||
qw.eq("algorithm_type", algorithmType).eq("device_type", deviceType);
|
||||
if (!isBlank(materialType)) {
|
||||
if ("Mixed".equals(materialType)) {
|
||||
qw.in("material_type", List.of("Mixed", "MIX"));
|
||||
} else {
|
||||
qw.eq("material_type", materialType);
|
||||
}
|
||||
} else {
|
||||
qw.and(wrapper -> wrapper.isNull("material_type").or().eq("material_type", ""));
|
||||
}
|
||||
@ -288,6 +354,16 @@ public class AlgorithmModelController {
|
||||
return Map.of("code", 0, "msg", "训练成功", "data", model);
|
||||
}
|
||||
|
||||
private String normalizeMaterialType(String raw) {
|
||||
if (raw == null) return null;
|
||||
String s = raw.trim();
|
||||
if (s.isEmpty()) return null;
|
||||
if ("U".equalsIgnoreCase(s)) return "U";
|
||||
if ("Pu".equalsIgnoreCase(s)) return "Pu";
|
||||
if ("Mixed".equalsIgnoreCase(s) || "MIX".equalsIgnoreCase(s)) return "Mixed";
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
private String currentUsername() {
|
||||
try {
|
||||
|
||||
@ -8,6 +8,9 @@ import com.yfd.platform.system.service.IUserService;
|
||||
|
||||
|
||||
import com.yfd.platform.annotation.Log;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
@ -18,6 +21,7 @@ import jakarta.annotation.Resource;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/critical-data")
|
||||
@ -121,6 +125,37 @@ public class CriticalDataController {
|
||||
return criticalDataService.importCriticalData(file, deviceType);
|
||||
}
|
||||
|
||||
@PostMapping("/v2/import")
|
||||
public boolean importCriticalDataV2(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam String deviceType) {
|
||||
return criticalDataService.importCriticalDataV2(file, deviceType);
|
||||
}
|
||||
|
||||
@PostMapping("/v2/validate")
|
||||
public Map<String, Object> validateCriticalDataV2(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam String deviceType) {
|
||||
return criticalDataService.validateCriticalDataV2(file, deviceType);
|
||||
}
|
||||
|
||||
@GetMapping("/v2/export")
|
||||
public ResponseEntity<byte[]> exportCriticalDataV2(@RequestParam String deviceType,
|
||||
@RequestParam(required = false) List<String> ids) {
|
||||
byte[] bytes = criticalDataService.exportCriticalDataV2(deviceType, ids);
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=critical_data_" + deviceType + ".xlsx")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
@GetMapping("/v2/template")
|
||||
public ResponseEntity<byte[]> templateCriticalDataV2(@RequestParam String deviceType) {
|
||||
byte[] bytes = criticalDataService.templateCriticalDataV2(deviceType);
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=critical_data_template_" + deviceType + ".xlsx")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -6,6 +6,11 @@ import com.yfd.business.css.domain.Device;
|
||||
import com.yfd.business.css.security.ProjectAccessHelper;
|
||||
import com.yfd.business.css.service.DeviceService;
|
||||
import com.yfd.platform.system.service.IUserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
@ -15,10 +20,16 @@ import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 设备管理接口(模板库与项目设备)
|
||||
* 包含基础增删改查,以及设备 V2 Excel 导入/校验/导出/模板接口(尺寸字段按 size-schema 收拢/展开)。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/devices")
|
||||
@Tag(name = "设备管理接口", description = "设备模板库/项目设备的增删改查与 Excel 导入导出")
|
||||
public class DeviceController {
|
||||
|
||||
@Resource
|
||||
@ -130,6 +141,99 @@ public class DeviceController {
|
||||
return deviceService.importDevices(file, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 4.2 导入设备(Excel,V2)
|
||||
* 输入参数:表单文件字段 file;可选 projectId/deviceType
|
||||
* 设计要点:支持按 deviceType 的 size-schema 将尺寸列收拢为 size(JSON);projectId 为空时默认写入 -1(模板库)
|
||||
* 权限:projectId != -1 时校验项目写权限
|
||||
* 输出参数:布尔值,表示是否导入成功
|
||||
* @param file Excel/CSV 文件
|
||||
* @param projectId 项目ID(可选,默认 -1)
|
||||
* @param deviceType 设备类型(可选)
|
||||
* @return 是否导入成功
|
||||
*/
|
||||
@PostMapping("/v2/import")
|
||||
@Operation(summary = "导入设备(V2)", description = "支持按 size-schema 收拢尺寸列为 size(JSON);projectId 为空默认 -1(模板库)")
|
||||
public boolean importDevicesV2(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(required = false) String projectId,
|
||||
@RequestParam(required = false) String deviceType) {
|
||||
String pid = (projectId == null || projectId.isBlank()) ? "-1" : projectId;
|
||||
if (!"-1".equals(pid)) {
|
||||
projectAccessHelper.assertCanWriteProject(pid);
|
||||
}
|
||||
return deviceService.importDevicesV2(file, pid, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 4.3 校验设备(Excel,V2)
|
||||
* 输入参数:表单文件字段 file;可选 projectId/deviceType
|
||||
* 设计要点:仅做解析与校验,不落库;返回 errors 与可用于导入的 _rows(前端可据此预览/提示)
|
||||
* 权限:projectId != -1 时校验项目写权限(与导入保持一致)
|
||||
* 输出参数:校验结果 Map(ok/errorRows/validRows/errors/_rows 等)
|
||||
* @param file Excel/CSV 文件
|
||||
* @param projectId 项目ID(可选,默认 -1)
|
||||
* @param deviceType 设备类型(可选)
|
||||
* @return 校验结果
|
||||
*/
|
||||
@PostMapping("/v2/validate")
|
||||
@Operation(summary = "校验设备(V2)", description = "仅解析与校验不落库;返回 errors 与可用于导入的 _rows")
|
||||
public Map<String, Object> validateDevicesV2(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(required = false) String projectId,
|
||||
@RequestParam(required = false) String deviceType) {
|
||||
String pid = (projectId == null || projectId.isBlank()) ? "-1" : projectId;
|
||||
if (!"-1".equals(pid)) {
|
||||
projectAccessHelper.assertCanWriteProject(pid);
|
||||
}
|
||||
return deviceService.validateDevicesV2(file, pid, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 4.4 导出设备(Excel,V2)
|
||||
* 输入参数:可选 projectId/deviceType/ids
|
||||
* 设计要点:按 size-schema 将 size(JSON) 展开为多列输出,便于人工编辑与回填
|
||||
* 权限:projectId != -1 时校验项目读权限
|
||||
* 输出参数:Excel 二进制(xlsx)
|
||||
* @param projectId 项目ID(可选,默认 -1)
|
||||
* @param deviceType 设备类型(可选)
|
||||
* @param ids 指定导出设备ID列表(可选;为空则按筛选条件导出)
|
||||
* @return xlsx 文件字节流
|
||||
*/
|
||||
@GetMapping("/v2/export")
|
||||
@Operation(summary = "导出设备(V2)", description = "按 size-schema 将 size(JSON) 展开为多列输出;支持按 ids 定向导出")
|
||||
public ResponseEntity<byte[]> exportDevicesV2(@RequestParam(required = false) String projectId,
|
||||
@RequestParam(required = false) String deviceType,
|
||||
@RequestParam(required = false) List<String> ids) {
|
||||
String pid = (projectId == null || projectId.isBlank()) ? "-1" : projectId;
|
||||
if (!"-1".equals(pid)) {
|
||||
projectAccessHelper.assertCanReadProject(pid);
|
||||
}
|
||||
byte[] bytes = deviceService.exportDevicesV2(pid, deviceType, ids);
|
||||
String fn = "devices_" + (deviceType == null || deviceType.isBlank() ? "all" : deviceType) + ".xlsx";
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fn)
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 4.5 下载导入模板(Excel,V2)
|
||||
* 输入参数:deviceType
|
||||
* 设计要点:模板表头与 size-schema 对齐,避免前端/后端/导入导出口径漂移
|
||||
* 输出参数:Excel 二进制(xlsx)
|
||||
* @param deviceType 设备类型
|
||||
* @return xlsx 模板字节流
|
||||
*/
|
||||
@GetMapping("/v2/template")
|
||||
@Operation(summary = "下载导入模板(V2)", description = "模板表头与 size-schema 对齐,便于导入导出与推理口径统一")
|
||||
public ResponseEntity<byte[]> templateDevicesV2(@RequestParam String deviceType) {
|
||||
byte[] bytes = deviceService.templateDevicesV2(deviceType);
|
||||
String fn = "device_template_" + deviceType + ".xlsx";
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fn)
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 5. 根据项目ID获取设备列表
|
||||
@ -183,6 +287,11 @@ public class DeviceController {
|
||||
return deviceService.page(page, qw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录账号 username(用于 creator/modifier 字段)
|
||||
* 未登录或取不到用户信息时返回 anonymous。
|
||||
* @return 当前登录账号 username
|
||||
*/
|
||||
private String currentUsername() {
|
||||
try {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package com.yfd.business.css.controller;
|
||||
|
||||
import com.yfd.business.css.meta.DeviceSizeSchema;
|
||||
import com.yfd.business.css.meta.DeviceSizeSchemaRegistry;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/devices/v2")
|
||||
@Tag(name = "设备元数据接口", description = "提供设备尺寸 size-schema 元数据,供前端动态渲染与校验使用")
|
||||
public class DeviceMetaController {
|
||||
@Resource
|
||||
private DeviceSizeSchemaRegistry registry;
|
||||
|
||||
@GetMapping("/size-schema")
|
||||
@Operation(summary = "获取指定设备类型的尺寸 schema", description = "返回 deviceType 对应的字段元数据(key/label/unit/required/order/min/max),用于前端动态渲染与与导入导出/推理口径对齐")
|
||||
public DeviceSizeSchema schema(@RequestParam String deviceType) {
|
||||
return registry.getSchema(deviceType);
|
||||
}
|
||||
|
||||
@GetMapping("/size-schema/all")
|
||||
@Operation(summary = "获取全部设备类型的尺寸 schema", description = "返回所有 deviceType 的 schema Map,适合前端一次性缓存")
|
||||
public Map<String, DeviceSizeSchema> all() {
|
||||
return registry.getAllSchemas();
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,9 @@ import com.yfd.business.css.domain.Material;
|
||||
import com.yfd.business.css.security.ProjectAccessHelper;
|
||||
import com.yfd.business.css.service.MaterialService;
|
||||
import com.yfd.platform.system.service.IUserService;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
@ -118,7 +121,7 @@ public class MaterialController {
|
||||
/**
|
||||
* 4. 导入物料(Excel/CSV)
|
||||
* 输入参数:表单文件字段 file
|
||||
* 模板表头:name, u_concentration, uo2_density, u_enrichment, pu_concentration, puo2_density, pu_isotope, hno3_acidity, h2c2o4_concentration, organic_ratio, moisture_content, custom_attrs
|
||||
* 模板表头:name, u_concentration, uo2_density, u_enrichment, pu_concentration, puo2_density, e_pu240, e_pu242, e_pu241, e_pu239, e_pu238, hno3_acidity, h2c2o4_concentration, organic_ratio, moisture_content, custom_attrs
|
||||
* 采集规则:仅采集上述字段,表头中多余字段不予采集;custom_attrs 校验为合法 JSON,非法或占位符(-、—、/、/)将置为 null;导入记录的 project_id 统一写入 -1
|
||||
* 输出参数:布尔值,表示是否导入成功
|
||||
* @param file Excel/CSV 文件
|
||||
@ -130,6 +133,30 @@ public class MaterialController {
|
||||
return materialService.importMaterials(file);
|
||||
}
|
||||
|
||||
@GetMapping("/export")
|
||||
public ResponseEntity<byte[]> exportMaterialsV2(@RequestParam String projectId,
|
||||
@RequestParam(required = false) List<String> ids,
|
||||
@RequestParam(required = false) String nameLike) {
|
||||
if (projectId != null && !projectId.isBlank() && !"-1".equals(projectId)) {
|
||||
projectAccessHelper.assertCanReadProject(projectId);
|
||||
}
|
||||
byte[] bytes = materialService.exportMaterialsV2(projectId, ids, nameLike);
|
||||
String fn = "materials.xlsx";
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fn)
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
@GetMapping("/v2/template")
|
||||
public ResponseEntity<byte[]> templateMaterialsV2() {
|
||||
byte[] bytes = materialService.templateMaterialsV2();
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=materials_template.xlsx")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 5. 根据物料名称搜索(可为空)并分页返回
|
||||
* 输入参数:查询参数 name(物料名称关键词,可为空),pageNum(页码,默认1),pageSize(每页条数,默认10)
|
||||
|
||||
@ -29,20 +29,47 @@ public class CriticalData implements Serializable {
|
||||
@TableField("device_type")
|
||||
private String deviceType;
|
||||
|
||||
@TableField("size")
|
||||
private String size;
|
||||
|
||||
@TableField("u_concentration")
|
||||
private BigDecimal uConcentration;
|
||||
|
||||
@TableField("u_enrichment")
|
||||
private BigDecimal uEnrichment;
|
||||
|
||||
@TableField("pu_concentration")
|
||||
private BigDecimal puConcentration;
|
||||
|
||||
@TableField("e_pu240")
|
||||
private BigDecimal ePu240;
|
||||
|
||||
@TableField("e_pu241")
|
||||
private BigDecimal ePu241;
|
||||
|
||||
@TableField("e_pu242")
|
||||
private BigDecimal ePu242;
|
||||
|
||||
@TableField("e_pu239")
|
||||
private BigDecimal ePu239;
|
||||
|
||||
@TableField("e_pu238")
|
||||
private BigDecimal ePu238;
|
||||
|
||||
/** 等效直径 */
|
||||
@TableField("diameter")
|
||||
@TableField(exist = false)
|
||||
private BigDecimal diameter;
|
||||
|
||||
/** 等效高度 */
|
||||
@TableField("height")
|
||||
@TableField(exist = false)
|
||||
private BigDecimal height;
|
||||
|
||||
/** 核材料浓度(U 或 Pu) */
|
||||
@TableField("fissile_concentration")
|
||||
@TableField(exist = false)
|
||||
private BigDecimal fissileConcentration;
|
||||
|
||||
/** 同位素丰度(铀富集度 或 Pu-240 占比) */
|
||||
@TableField("isotopic_abundance")
|
||||
@TableField(exist = false)
|
||||
private BigDecimal isotopicAbundance;
|
||||
|
||||
/** 扩展物理/算法特征 - JSON格式 */
|
||||
|
||||
@ -46,6 +46,21 @@ public class Material implements Serializable {
|
||||
@TableField("pu_isotope")
|
||||
private BigDecimal puIsotope;
|
||||
|
||||
@TableField("e_pu240")
|
||||
private BigDecimal ePu240;
|
||||
|
||||
@TableField("e_pu242")
|
||||
private BigDecimal ePu242;
|
||||
|
||||
@TableField("e_pu241")
|
||||
private BigDecimal ePu241;
|
||||
|
||||
@TableField("e_pu239")
|
||||
private BigDecimal ePu239;
|
||||
|
||||
@TableField("e_pu238")
|
||||
private BigDecimal ePu238;
|
||||
|
||||
@TableField("hno3_acidity")
|
||||
private BigDecimal hno3Acidity;
|
||||
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
package com.yfd.business.css.meta;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceSizeField {
|
||||
/**
|
||||
* 扁平化 size JSON 的 key,必须与:
|
||||
* 1) device.size(JSON) 内的键
|
||||
* 2) 设备导入/导出展开列名
|
||||
* 3) 训练/推理侧特征名(feature_map_snapshot)
|
||||
* 保持一致,避免口径漂移。
|
||||
*/
|
||||
private String key;
|
||||
/**
|
||||
* 前端展示用中文名称。
|
||||
*/
|
||||
private String label;
|
||||
/**
|
||||
* 单位(默认 cm)。
|
||||
*/
|
||||
private String unit;
|
||||
/**
|
||||
* 是否必填(用于前端渲染与导入校验)。
|
||||
*/
|
||||
private boolean required;
|
||||
/**
|
||||
* 展示/导出列顺序(从 1 开始)。
|
||||
*/
|
||||
private int order;
|
||||
/**
|
||||
* 最小值(可选,用于前端与校验)。
|
||||
*/
|
||||
private Double min;
|
||||
/**
|
||||
* 最大值(可选,用于前端与校验)。
|
||||
*/
|
||||
private Double max;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.yfd.business.css.meta;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class DeviceSizeSchema {
|
||||
/**
|
||||
* 设备类型(与 Device.deviceType 取值一致,例如 FlatTank/CylindricalTank 等)。
|
||||
*/
|
||||
private String deviceType;
|
||||
/**
|
||||
* schema 版本号;用于后续扩展/兼容(例如字段新增、命名调整等)。
|
||||
*/
|
||||
private String schemaVersion;
|
||||
/**
|
||||
* 尺寸字段元数据列表(有序),用于:
|
||||
* - 前端动态渲染表单/表格列
|
||||
* - 设备导入/导出/模板表头
|
||||
* - 与推理侧扁平化特征键保持一致
|
||||
*/
|
||||
private List<DeviceSizeField> fields;
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
package com.yfd.business.css.meta;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class DeviceSizeSchemaRegistry {
|
||||
private final Map<String, DeviceSizeSchema> schemas;
|
||||
|
||||
public DeviceSizeSchemaRegistry() {
|
||||
this.schemas = buildSchemas();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定设备类型的尺寸 schema。
|
||||
* 注意:deviceType 取值必须与设备表/业务枚举一致;若传入未知类型将返回 null。
|
||||
*/
|
||||
public DeviceSizeSchema getSchema(String deviceType) {
|
||||
if (deviceType == null) return null;
|
||||
return schemas.get(deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有设备类型的尺寸 schema(用于前端缓存与动态渲染)。
|
||||
* 注意:返回的 Map 为注册表内部持有对象;调用方应只读使用,不要修改。
|
||||
*/
|
||||
public Map<String, DeviceSizeSchema> getAllSchemas() {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定设备类型的扁平化 size keys(按 order 有序)。
|
||||
* 注意:这些 key 同时作为导入/导出的 Excel 列名与 device.size JSON 的键,必须保持稳定。
|
||||
*/
|
||||
public List<String> getSizeKeys(String deviceType) {
|
||||
DeviceSizeSchema s = getSchema(deviceType);
|
||||
if (s == null || s.getFields() == null) return List.of();
|
||||
List<String> out = new ArrayList<>();
|
||||
for (DeviceSizeField f : s.getFields()) out.add(f.getKey());
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Map<String, DeviceSizeSchema> buildSchemas() {
|
||||
Map<String, DeviceSizeSchema> map = new LinkedHashMap<>();
|
||||
map.put("FlatTank", schema("FlatTank", fields(
|
||||
req(1, "length", "长度"),
|
||||
req(2, "width", "宽度"),
|
||||
req(3, "height", "高度")
|
||||
)));
|
||||
map.put("CylindricalTank", schema("CylindricalTank", fields(
|
||||
req(1, "diameter", "直径"),
|
||||
req(2, "height", "高度")
|
||||
)));
|
||||
map.put("AnnularTank", schema("AnnularTank", fields(
|
||||
req(1, "diameter", "环形槽外径"),
|
||||
req(2, "height", "高度")
|
||||
)));
|
||||
map.put("TubeBundleTank", schema("TubeBundleTank", fields(
|
||||
req(1, "diameter", "管束槽外径"),
|
||||
req(2, "height", "高度")
|
||||
)));
|
||||
map.put("ExtractionColumn", schema("ExtractionColumn", fields(
|
||||
req(1, "upper_diameter", "上扩大段直径"),
|
||||
req(2, "upper_height", "上扩大段高度"),
|
||||
req(3, "tray_diameter", "板段直径"),
|
||||
req(4, "tray_height", "板段高度"),
|
||||
req(5, "lower_diameter", "下扩大段直径"),
|
||||
req(6, "lower_height", "下扩大段高度")
|
||||
)));
|
||||
map.put("PulsedCylindricalColumn", schema("PulsedCylindricalColumn", fields(
|
||||
req(1, "upper_inner_diameter", "上扩大段内径"),
|
||||
req(2, "upper_outer_diameter", "上扩大段外径"),
|
||||
req(3, "upper_height", "上扩大段高度"),
|
||||
req(4, "tray_diameter", "板段直径"),
|
||||
req(5, "tray_height", "板段高度"),
|
||||
req(6, "lower_inner_diameter", "下扩大段内径"),
|
||||
req(7, "lower_outer_diameter", "下扩大段外径"),
|
||||
req(8, "lower_height", "下扩大段高度")
|
||||
)));
|
||||
map.put("PulsedAnnularColumn", schema("PulsedAnnularColumn", fields(
|
||||
req(1, "upper_inner_diameter", "上扩大段内径"),
|
||||
req(2, "upper_outer_diameter", "上扩大段外径"),
|
||||
req(3, "upper_height", "上扩大段高度"),
|
||||
req(4, "tray_inner_diameter", "板段内径"),
|
||||
req(5, "tray_outer_diameter", "板段外径"),
|
||||
req(6, "tray_height", "板段高度"),
|
||||
req(7, "lower_inner_diameter", "下扩大段内径"),
|
||||
req(8, "lower_outer_diameter", "下扩大段外径"),
|
||||
req(9, "lower_height", "下扩大段高度")
|
||||
)));
|
||||
map.put("FluidizedBed", schema("FluidizedBed", fields(
|
||||
req(1, "expanded_diameter", "扩大段直径"),
|
||||
req(2, "expanded_height", "扩大段高度"),
|
||||
req(3, "transition_height", "过渡段高度"),
|
||||
req(4, "reaction_diameter", "反应段直径"),
|
||||
req(5, "reaction_height", "反应段高度")
|
||||
)));
|
||||
map.put("ACFTank", schema("ACFTank", fields(
|
||||
req(1, "cylinder_diameter", "环形圆柱外径"),
|
||||
req(2, "cylinder_height", "环形圆柱高度"),
|
||||
req(3, "bottom_diameter", "圆锥台底部直径"),
|
||||
req(4, "bottom_height", "圆锥台底部高度"),
|
||||
req(5, "scab_thickness", "结疤厚度"),
|
||||
req(6, "scab_height", "结疤高度")
|
||||
)));
|
||||
return map;
|
||||
}
|
||||
|
||||
private static DeviceSizeSchema schema(String deviceType, List<DeviceSizeField> fields) {
|
||||
DeviceSizeSchema s = new DeviceSizeSchema();
|
||||
s.setDeviceType(deviceType);
|
||||
s.setSchemaVersion("v2");
|
||||
s.setFields(fields);
|
||||
return s;
|
||||
}
|
||||
|
||||
private static List<DeviceSizeField> fields(DeviceSizeField... fs) {
|
||||
List<DeviceSizeField> out = new ArrayList<>();
|
||||
for (DeviceSizeField f : fs) out.add(f);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static DeviceSizeField req(int order, String key, String label) {
|
||||
DeviceSizeField f = new DeviceSizeField();
|
||||
f.setOrder(order);
|
||||
f.setKey(key);
|
||||
f.setLabel(label);
|
||||
f.setUnit("cm");
|
||||
f.setRequired(true);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yfd.business.css.domain.CriticalData;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface CriticalDataService extends IService<CriticalData> {
|
||||
/**
|
||||
@ -11,5 +12,12 @@ public interface CriticalDataService extends IService<CriticalData> {
|
||||
*/
|
||||
boolean importCriticalData(MultipartFile file, String deviceType);
|
||||
|
||||
boolean importCriticalDataV2(MultipartFile file, String deviceType);
|
||||
|
||||
Map<String, Object> validateCriticalDataV2(MultipartFile file, String deviceType);
|
||||
|
||||
byte[] exportCriticalDataV2(String deviceType, List<String> ids);
|
||||
|
||||
byte[] templateCriticalDataV2(String deviceType);
|
||||
|
||||
}
|
||||
|
||||
@ -3,12 +3,22 @@ package com.yfd.business.css.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yfd.business.css.domain.Device;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
public interface DeviceService extends IService<Device> {
|
||||
/**
|
||||
* 导入设备
|
||||
*/
|
||||
boolean importDevices(MultipartFile file, String deviceType);
|
||||
|
||||
boolean importDevicesV2(MultipartFile file, String projectId, String deviceType);
|
||||
|
||||
Map<String, Object> validateDevicesV2(MultipartFile file, String projectId, String deviceType);
|
||||
|
||||
byte[] exportDevicesV2(String projectId, String deviceType, List<String> ids);
|
||||
|
||||
byte[] templateDevicesV2(String deviceType);
|
||||
|
||||
boolean createDevice(Device device) ;
|
||||
|
||||
boolean saveOrUpdateByBusiness(Device device);
|
||||
|
||||
@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yfd.business.css.domain.Device;
|
||||
import com.yfd.business.css.domain.Material;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import java.util.List;
|
||||
|
||||
public interface MaterialService extends IService<Material> {
|
||||
/**
|
||||
@ -17,4 +18,8 @@ public interface MaterialService extends IService<Material> {
|
||||
boolean saveMaterial(Material material);
|
||||
|
||||
boolean saveOrUpdateByBusiness(Material material);
|
||||
|
||||
byte[] exportMaterialsV2(String projectId, List<String> ids, String nameLike);
|
||||
|
||||
byte[] templateMaterialsV2();
|
||||
}
|
||||
|
||||
@ -36,15 +36,18 @@ public class AlgorithmModelServiceImpl extends ServiceImpl<AlgorithmModelMapper,
|
||||
@Override
|
||||
public AlgorithmModel getCurrentModel(String algorithmType, String deviceType, String materialType) {
|
||||
log.debug("Querying current model for algorithmType: {}, deviceType: {}, materialType: {}", algorithmType, deviceType, materialType);
|
||||
String mt = materialType == null ? null : materialType.trim();
|
||||
if (mt != null && mt.isEmpty()) mt = null;
|
||||
String mt = normalizeMaterialType(materialType);
|
||||
|
||||
QueryWrapper<AlgorithmModel> qw = new QueryWrapper<>();
|
||||
qw.eq("algorithm_type", algorithmType)
|
||||
.eq("device_type", deviceType)
|
||||
.eq("is_current", 1);
|
||||
if (mt != null) {
|
||||
if ("Mixed".equals(mt)) {
|
||||
qw.in("material_type", List.of("Mixed", "MIX"));
|
||||
} else {
|
||||
qw.eq("material_type", mt);
|
||||
}
|
||||
} else {
|
||||
qw.and(w -> w.isNull("material_type").or().eq("material_type", ""));
|
||||
}
|
||||
@ -63,6 +66,16 @@ public class AlgorithmModelServiceImpl extends ServiceImpl<AlgorithmModelMapper,
|
||||
return null;
|
||||
}
|
||||
|
||||
private String normalizeMaterialType(String raw) {
|
||||
if (raw == null) return null;
|
||||
String s = raw.trim();
|
||||
if (s.isEmpty()) return null;
|
||||
if ("U".equalsIgnoreCase(s)) return "U";
|
||||
if ("Pu".equalsIgnoreCase(s)) return "Pu";
|
||||
if ("Mixed".equalsIgnoreCase(s) || "MIX".equalsIgnoreCase(s)) return "Mixed";
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteBatchWithCheck(List<String> ids) {
|
||||
|
||||
@ -10,4 +10,30 @@ import org.springframework.stereotype.Service;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AlgorithmServiceImpl extends ServiceImpl<AlgorithmMapper, Algorithm> implements AlgorithmService {
|
||||
@Override
|
||||
public boolean save(Algorithm entity) {
|
||||
normalizeJsonFields(entity);
|
||||
return super.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateById(Algorithm entity) {
|
||||
normalizeJsonFields(entity);
|
||||
return super.updateById(entity);
|
||||
}
|
||||
|
||||
private void normalizeJsonFields(Algorithm a) {
|
||||
if (a == null) return;
|
||||
a.setInputParams(blankToDefaultJson(a.getInputParams(), "{}"));
|
||||
a.setOutputParams(blankToDefaultJson(a.getOutputParams(), "{}"));
|
||||
a.setSupportedDeviceTypes(blankToDefaultJson(a.getSupportedDeviceTypes(), "[]"));
|
||||
a.setDefaultHyperParams(blankToDefaultJson(a.getDefaultHyperParams(), "{}"));
|
||||
}
|
||||
|
||||
private String blankToDefaultJson(String s, String defaultJson) {
|
||||
if (s == null) return defaultJson;
|
||||
String t = s.trim();
|
||||
if (t.isEmpty()) return defaultJson;
|
||||
return t;
|
||||
}
|
||||
}
|
||||
@ -24,10 +24,16 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import java.time.LocalDateTime;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@Service
|
||||
@ -62,6 +68,81 @@ public class CriticalDataServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean importCriticalDataV2(MultipartFile file, String deviceType) {
|
||||
Map<String, Object> res = validateCriticalDataV2(file, deviceType);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<CriticalData> list = (List<CriticalData>) res.get("_rows");
|
||||
if (list == null || list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return this.saveBatch(list, 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> validateCriticalDataV2(MultipartFile file, String deviceType) {
|
||||
Map<String, Object> out = new HashMap<>();
|
||||
List<Map<String, Object>> errors = new ArrayList<>();
|
||||
out.put("errors", errors);
|
||||
try {
|
||||
String name = file.getOriginalFilename();
|
||||
if (name == null) {
|
||||
out.put("ok", false);
|
||||
out.put("msg", "文件名为空");
|
||||
return out;
|
||||
}
|
||||
if (deviceType == null || deviceType.isBlank()) {
|
||||
out.put("ok", false);
|
||||
out.put("msg", "deviceType 必填");
|
||||
return out;
|
||||
}
|
||||
String lower = name.toLowerCase();
|
||||
if (lower.endsWith(".xlsx")) {
|
||||
return validateExcelV2(new XSSFWorkbook(file.getInputStream()), deviceType);
|
||||
} else if (lower.endsWith(".xls")) {
|
||||
return validateExcelV2(new HSSFWorkbook(file.getInputStream()), deviceType);
|
||||
} else {
|
||||
out.put("ok", false);
|
||||
out.put("msg", "不支持的文件类型");
|
||||
return out;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
out.put("ok", false);
|
||||
out.put("msg", "解析失败: " + e.getMessage());
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] exportCriticalDataV2(String deviceType, List<String> ids) {
|
||||
try {
|
||||
if (deviceType == null || deviceType.isBlank()) return new byte[0];
|
||||
var qw = new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<CriticalData>()
|
||||
.eq("device_type", deviceType)
|
||||
.orderByDesc("created_at");
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
qw.in("critical_id", ids);
|
||||
}
|
||||
List<CriticalData> list = this.list(qw);
|
||||
byte[] bytes = buildExportWorkbookBytes(deviceType, list);
|
||||
return bytes == null ? new byte[0] : bytes;
|
||||
} catch (Exception e) {
|
||||
log.error("critical-data export v2 failed", e);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] templateCriticalDataV2(String deviceType) {
|
||||
try {
|
||||
if (deviceType == null || deviceType.isBlank()) return new byte[0];
|
||||
return buildTemplateWorkbookBytes(deviceType);
|
||||
} catch (Exception e) {
|
||||
log.error("critical-data template v2 failed", e);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
private boolean importExcel(Workbook workbook, String deviceType) {
|
||||
try (Workbook wb = workbook) {
|
||||
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
|
||||
@ -141,6 +222,337 @@ public class CriticalDataServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> validateExcelV2(Workbook workbook, String deviceType) {
|
||||
try (Workbook wb = workbook) {
|
||||
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
|
||||
evaluator.evaluateAll();
|
||||
DataFormatter formatter = new DataFormatter();
|
||||
Sheet sheet = wb.getSheetAt(0);
|
||||
if (sheet == null) {
|
||||
return Map.of("ok", false, "msg", "sheet为空", "errors", List.of());
|
||||
}
|
||||
Row headerRow = sheet.getRow(0);
|
||||
if (headerRow == null) {
|
||||
return Map.of("ok", false, "msg", "表头为空", "errors", List.of());
|
||||
}
|
||||
Map<String, Integer> idx = new HashMap<>();
|
||||
for (int i = 0; i < headerRow.getLastCellNum(); i++) {
|
||||
Cell c = headerRow.getCell(i);
|
||||
if (c == null) continue;
|
||||
String key = c.getStringCellValue();
|
||||
if (key != null) idx.put(key.trim().toLowerCase(), i);
|
||||
}
|
||||
|
||||
List<String> sizeCols = getSizeColumnsByDeviceType(deviceType);
|
||||
boolean hasSizeJsonHeader = idx.containsKey("size");
|
||||
boolean hasAnySizePartHeader = false;
|
||||
for (String k : sizeCols) {
|
||||
if (idx.containsKey(k)) {
|
||||
hasAnySizePartHeader = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!idx.containsKey("keff_value")) {
|
||||
return Map.of("ok", false, "msg", "缺少表头: keff_value", "errors", List.of());
|
||||
}
|
||||
boolean hasUConcHeader = idx.containsKey("u_concentration");
|
||||
boolean hasUEnrHeader = idx.containsKey("u_enrichment");
|
||||
boolean hasPuConcHeader = idx.containsKey("pu_concentration");
|
||||
if (!hasUConcHeader && !hasPuConcHeader && !hasUEnrHeader) {
|
||||
return Map.of("ok", false, "msg", "缺少表头: u_concentration/pu_concentration/u_enrichment", "errors", List.of());
|
||||
}
|
||||
if (!hasSizeJsonHeader && !hasAnySizePartHeader) {
|
||||
return Map.of("ok", false, "msg", "缺少表头: size 或任一尺寸列", "errors", List.of());
|
||||
}
|
||||
|
||||
List<Map<String, Object>> errors = new ArrayList<>();
|
||||
List<CriticalData> rows = new ArrayList<>();
|
||||
|
||||
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null) continue;
|
||||
|
||||
Integer sizeIndex = idx.get("size");
|
||||
String sizeRaw = sizeIndex == null ? null : cleanString(getString(row, sizeIndex));
|
||||
String sizeJson = null;
|
||||
if (sizeRaw != null) {
|
||||
sizeJson = validateJson(sizeRaw);
|
||||
if (sizeJson == null) {
|
||||
errors.add(err(r, "size 非法JSON"));
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
JsonNode node = objectMapper.readTree(sizeJson);
|
||||
if (node != null && node.isObject()) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
node.fields().forEachRemaining(e -> {
|
||||
JsonNode v = e.getValue();
|
||||
if (v == null || v.isNull()) return;
|
||||
if (v.isNumber()) {
|
||||
BigDecimal d = v.decimalValue();
|
||||
m.put(e.getKey(), roundScale(d, 3));
|
||||
} else if (v.isTextual()) {
|
||||
m.put(e.getKey(), cleanString(v.asText()));
|
||||
} else {
|
||||
m.put(e.getKey(), v.toString());
|
||||
}
|
||||
});
|
||||
sizeJson = objectMapper.writeValueAsString(m);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
} else {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
for (String k : sizeCols) {
|
||||
Integer colIndex = idx.get(k);
|
||||
if (colIndex == null) continue;
|
||||
BigDecimal v = getDecimalFlexible(row, colIndex, evaluator, formatter);
|
||||
if (v != null) {
|
||||
m.put(k, roundScale(v, 3));
|
||||
}
|
||||
}
|
||||
if (m.isEmpty()) {
|
||||
errors.add(err(r, "尺寸列为空"));
|
||||
continue;
|
||||
}
|
||||
sizeJson = objectMapper.writeValueAsString(m);
|
||||
}
|
||||
|
||||
Integer uConcIndex = idx.get("u_concentration");
|
||||
Integer uEnrIndex = idx.get("u_enrichment");
|
||||
Integer puConcIndex = idx.get("pu_concentration");
|
||||
Integer ePu240Index = idx.get("e_pu240");
|
||||
Integer ePu241Index = idx.get("e_pu241");
|
||||
Integer ePu242Index = idx.get("e_pu242");
|
||||
Integer ePu239Index = idx.get("e_pu239");
|
||||
Integer ePu238Index = idx.get("e_pu238");
|
||||
Integer keffIndex = idx.get("keff_value");
|
||||
|
||||
BigDecimal uConc = uConcIndex == null ? null : getDecimalFlexible(row, uConcIndex, evaluator, formatter);
|
||||
BigDecimal uEnr = uEnrIndex == null ? null : getDecimalFlexible(row, uEnrIndex, evaluator, formatter);
|
||||
BigDecimal puConc = puConcIndex == null ? null : getDecimalFlexible(row, puConcIndex, evaluator, formatter);
|
||||
BigDecimal ePu240 = ePu240Index == null ? null : getDecimalFlexible(row, ePu240Index, evaluator, formatter);
|
||||
BigDecimal ePu241 = ePu241Index == null ? null : getDecimalFlexible(row, ePu241Index, evaluator, formatter);
|
||||
BigDecimal ePu242 = ePu242Index == null ? null : getDecimalFlexible(row, ePu242Index, evaluator, formatter);
|
||||
BigDecimal ePu239 = ePu239Index == null ? null : getDecimalFlexible(row, ePu239Index, evaluator, formatter);
|
||||
BigDecimal ePu238 = ePu238Index == null ? null : getDecimalFlexible(row, ePu238Index, evaluator, formatter);
|
||||
BigDecimal keff = keffIndex == null ? null : getDecimalFlexible(row, keffIndex, evaluator, formatter);
|
||||
|
||||
uConc = roundScale(uConc, 3);
|
||||
puConc = roundScale(puConc, 3);
|
||||
uEnr = roundScale(uEnr, 6);
|
||||
ePu240 = roundScale(ePu240, 6);
|
||||
ePu241 = roundScale(ePu241, 6);
|
||||
ePu242 = roundScale(ePu242, 6);
|
||||
ePu239 = roundScale(ePu239, 6);
|
||||
ePu238 = roundScale(ePu238, 6);
|
||||
keff = roundScale(keff, 6);
|
||||
|
||||
Integer extraIndex = idx.get("extra_features");
|
||||
String extraRaw = extraIndex == null ? null : cleanString(getString(row, extraIndex));
|
||||
String extra = validateJson(extraRaw);
|
||||
if (extraRaw != null && extra == null) {
|
||||
errors.add(err(r, "extra_features 非法JSON"));
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> rowMiss = new ArrayList<>();
|
||||
if (keff == null) rowMiss.add("keff_value");
|
||||
if (sizeJson == null || sizeJson.isBlank()) rowMiss.add("size");
|
||||
if (uConc == null && puConc == null && uEnr == null) rowMiss.add("u_concentration/pu_concentration/u_enrichment");
|
||||
if (!rowMiss.isEmpty()) {
|
||||
errors.add(err(r, "缺少字段: " + String.join(",", rowMiss)));
|
||||
continue;
|
||||
}
|
||||
if (uEnr != null && (uEnr.compareTo(BigDecimal.ZERO) < 0 || uEnr.compareTo(BigDecimal.ONE) > 0)) {
|
||||
errors.add(err(r, "u_enrichment 超范围(0-1)"));
|
||||
continue;
|
||||
}
|
||||
if (ePu240 != null && (ePu240.compareTo(BigDecimal.ZERO) < 0 || ePu240.compareTo(BigDecimal.ONE) > 0)) {
|
||||
errors.add(err(r, "e_pu240 超范围(0-1)"));
|
||||
continue;
|
||||
}
|
||||
|
||||
CriticalData cd = new CriticalData();
|
||||
cd.setDeviceType(deviceType);
|
||||
cd.setSize(sizeJson);
|
||||
cd.setUConcentration(uConc);
|
||||
cd.setUEnrichment(uEnr);
|
||||
cd.setPuConcentration(puConc);
|
||||
cd.setEPu240(ePu240);
|
||||
cd.setEPu241(ePu241);
|
||||
cd.setEPu242(ePu242);
|
||||
cd.setEPu239(ePu239);
|
||||
cd.setEPu238(ePu238);
|
||||
cd.setExtraFeatures(extra);
|
||||
cd.setKeffValue(keff);
|
||||
cd.setCreatedAt(LocalDateTime.now());
|
||||
cd.setUpdatedAt(LocalDateTime.now());
|
||||
cd.setModifier(currentUsername());
|
||||
rows.add(cd);
|
||||
}
|
||||
|
||||
Map<String, Object> out = new HashMap<>();
|
||||
out.put("ok", !rows.isEmpty());
|
||||
out.put("msg", errors.isEmpty() ? "OK" : ("存在错误: " + errors.size()));
|
||||
out.put("totalRows", sheet.getLastRowNum());
|
||||
out.put("validRows", rows.size());
|
||||
out.put("errorRows", errors.size());
|
||||
out.put("errors", errors.size() > 50 ? errors.subList(0, 50) : errors);
|
||||
out.put("_rows", rows);
|
||||
return out;
|
||||
} catch (Exception e) {
|
||||
return Map.of("ok", false, "msg", "解析失败: " + e.getMessage(), "errors", List.of());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> err(int row, String msg) {
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("row", row);
|
||||
m.put("msg", msg);
|
||||
return m;
|
||||
}
|
||||
|
||||
private List<String> getSizeColumnsByDeviceType(String deviceType) {
|
||||
if (deviceType == null) return Collections.emptyList();
|
||||
return switch (deviceType) {
|
||||
case "FlatTank" -> List.of("length", "width", "height");
|
||||
case "CylindricalTank" -> List.of("diameter", "height");
|
||||
case "AnnularTank" -> List.of("diameter", "height");
|
||||
case "TubeBundleTank" -> List.of("diameter", "height");
|
||||
case "ExtractionColumn" -> List.of(
|
||||
"upper_diameter", "upper_height",
|
||||
"tray_diameter", "tray_height",
|
||||
"lower_diameter", "lower_height"
|
||||
);
|
||||
case "PulsedCylindricalColumn" -> List.of(
|
||||
"upper_inner_diameter", "upper_outer_diameter", "upper_height",
|
||||
"tray_diameter", "tray_height",
|
||||
"lower_inner_diameter", "lower_outer_diameter", "lower_height"
|
||||
);
|
||||
case "PulsedAnnularColumn" -> List.of(
|
||||
"upper_inner_diameter", "upper_outer_diameter", "upper_height",
|
||||
"tray_inner_diameter", "tray_outer_diameter", "tray_height",
|
||||
"lower_inner_diameter", "lower_outer_diameter", "lower_height"
|
||||
);
|
||||
case "FluidizedBed" -> List.of(
|
||||
"expanded_diameter", "expanded_height",
|
||||
"transition_height",
|
||||
"reaction_diameter", "reaction_height"
|
||||
);
|
||||
case "ACFTank" -> List.of(
|
||||
"cylinder_diameter", "cylinder_height",
|
||||
"bottom_diameter", "bottom_height",
|
||||
"scab_thickness", "scab_height"
|
||||
);
|
||||
default -> List.of("diameter", "height");
|
||||
};
|
||||
}
|
||||
|
||||
private byte[] buildTemplateWorkbookBytes(String deviceType) throws Exception {
|
||||
List<String> sizeCols = getSizeColumnsByDeviceType(deviceType);
|
||||
List<String> headers = new ArrayList<>();
|
||||
headers.add("device_type");
|
||||
headers.addAll(sizeCols);
|
||||
headers.add("size");
|
||||
headers.add("u_concentration");
|
||||
headers.add("u_enrichment");
|
||||
headers.add("pu_concentration");
|
||||
headers.add("e_pu240");
|
||||
headers.add("e_pu241");
|
||||
headers.add("e_pu242");
|
||||
headers.add("e_pu239");
|
||||
headers.add("e_pu238");
|
||||
headers.add("extra_features");
|
||||
headers.add("keff_value");
|
||||
|
||||
try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
||||
Sheet sheet = wb.createSheet("critical_data");
|
||||
Row hr = sheet.createRow(0);
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
Cell c = hr.createCell(i);
|
||||
c.setCellValue(headers.get(i));
|
||||
}
|
||||
Row r1 = sheet.createRow(1);
|
||||
r1.createCell(0).setCellValue(deviceType);
|
||||
wb.write(bos);
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] buildExportWorkbookBytes(String deviceType, List<CriticalData> list) throws Exception {
|
||||
List<String> sizeCols = getSizeColumnsByDeviceType(deviceType);
|
||||
List<String> headers = new ArrayList<>();
|
||||
headers.addAll(sizeCols);
|
||||
headers.add("u_concentration");
|
||||
headers.add("u_enrichment");
|
||||
headers.add("pu_concentration");
|
||||
headers.add("e_pu240");
|
||||
headers.add("e_pu241");
|
||||
headers.add("e_pu242");
|
||||
headers.add("e_pu239");
|
||||
headers.add("e_pu238");
|
||||
headers.add("extra_features");
|
||||
headers.add("keff_value");
|
||||
|
||||
try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
||||
Sheet sheet = wb.createSheet("critical_data");
|
||||
Row hr = sheet.createRow(0);
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
hr.createCell(i).setCellValue(headers.get(i));
|
||||
}
|
||||
int rowNum = 1;
|
||||
for (CriticalData cd : list) {
|
||||
Row r = sheet.createRow(rowNum++);
|
||||
int col = 0;
|
||||
Map<String, String> flat = flattenSize(cd.getSize(), sizeCols);
|
||||
for (String k : sizeCols) {
|
||||
r.createCell(col++).setCellValue(nvl(flat.get(k)));
|
||||
}
|
||||
|
||||
r.createCell(col++).setCellValue(nvl(toStr(cd.getUConcentration())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(cd.getUEnrichment())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(cd.getPuConcentration())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(cd.getEPu240())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(cd.getEPu241())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(cd.getEPu242())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(cd.getEPu239())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(cd.getEPu238())));
|
||||
r.createCell(col++).setCellValue(nvl(cd.getExtraFeatures()));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(cd.getKeffValue())));
|
||||
}
|
||||
wb.write(bos);
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> flattenSize(String sizeJson, List<String> cols) {
|
||||
Map<String, String> out = new HashMap<>();
|
||||
if (cols == null || cols.isEmpty()) return out;
|
||||
if (sizeJson == null || sizeJson.isBlank()) return out;
|
||||
try {
|
||||
JsonNode node = objectMapper.readTree(sizeJson);
|
||||
for (String k : cols) {
|
||||
JsonNode v = node.get(k);
|
||||
if (v == null || v.isNull()) continue;
|
||||
if (v.isNumber()) out.put(k, v.numberValue().toString());
|
||||
else if (v.isTextual()) out.put(k, v.asText());
|
||||
else out.put(k, v.toString());
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private String toStr(BigDecimal d) {
|
||||
return d == null ? null : d.stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
private String nvl(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
private String getString(Row row, int i) {
|
||||
Cell c = row.getCell(i);
|
||||
if (c == null) return null;
|
||||
@ -155,8 +567,28 @@ public class CriticalDataServiceImpl
|
||||
private BigDecimal getDecimalFlexible(Row row, int i, FormulaEvaluator evaluator, DataFormatter formatter) {
|
||||
Cell c = row.getCell(i);
|
||||
if (c == null) return null;
|
||||
switch (c.getCellType()) {
|
||||
case NUMERIC:
|
||||
return BigDecimal.valueOf(c.getNumericCellValue());
|
||||
case STRING:
|
||||
return parseDecimal(cleanString(c.getStringCellValue()));
|
||||
case FORMULA: {
|
||||
var cv = evaluator.evaluate(c);
|
||||
if (cv == null) return null;
|
||||
switch (cv.getCellType()) {
|
||||
case NUMERIC:
|
||||
return BigDecimal.valueOf(cv.getNumberValue());
|
||||
case STRING:
|
||||
return parseDecimal(cleanString(cv.getStringValue()));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
String txt = formatter.formatCellValue(c, evaluator);
|
||||
return parseDecimal(txt);
|
||||
return parseDecimal(cleanString(txt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal parseDecimal(String s) {
|
||||
@ -188,6 +620,15 @@ public class CriticalDataServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal roundScale(BigDecimal v, int scale) {
|
||||
if (v == null) return null;
|
||||
try {
|
||||
return v.setScale(scale, RoundingMode.HALF_UP);
|
||||
} catch (Exception ignored) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPlaceholder(String s) {
|
||||
if (s == null) return false;
|
||||
String t = s.trim();
|
||||
|
||||
@ -17,6 +17,8 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.DataFormatter;
|
||||
import org.apache.poi.ss.usermodel.FormulaEvaluator;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
@ -24,11 +26,15 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.yfd.business.css.meta.DeviceSizeSchemaRegistry;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@ -40,6 +46,8 @@ public class DeviceServiceImpl
|
||||
private ObjectMapper objectMapper;
|
||||
@Resource
|
||||
private IUserService userService;
|
||||
@Resource
|
||||
private DeviceSizeSchemaRegistry deviceSizeSchemaRegistry;
|
||||
@Override
|
||||
public boolean importDevices(MultipartFile file, String deviceType) {
|
||||
try {
|
||||
@ -63,6 +71,64 @@ public class DeviceServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean importDevicesV2(MultipartFile file, String projectId, String deviceType) {
|
||||
Map<String, Object> res = validateDevicesV2(file, projectId, deviceType);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Device> list = (List<Device>) res.get("_rows");
|
||||
if (list == null || list.isEmpty()) return false;
|
||||
return this.saveBatch(list, 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> validateDevicesV2(MultipartFile file, String projectId, String deviceType) {
|
||||
try {
|
||||
String name = file.getOriginalFilename();
|
||||
if (name == null) return Map.of("ok", false, "msg", "文件名为空", "errors", List.of());
|
||||
String lower = name.toLowerCase();
|
||||
if (lower.endsWith(".xlsx")) {
|
||||
return validateExcelV2(new XSSFWorkbook(file.getInputStream()), projectId, deviceType);
|
||||
} else if (lower.endsWith(".xls")) {
|
||||
return validateExcelV2(new HSSFWorkbook(file.getInputStream()), projectId, deviceType);
|
||||
} else {
|
||||
return Map.of("ok", false, "msg", "不支持的文件类型", "errors", List.of());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return Map.of("ok", false, "msg", "解析失败: " + e.getMessage(), "errors", List.of());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] exportDevicesV2(String projectId, String deviceType, List<String> ids) {
|
||||
try {
|
||||
var qw = new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<Device>()
|
||||
.eq("project_id", projectId == null || projectId.isBlank() ? "-1" : projectId)
|
||||
.orderByDesc("created_at");
|
||||
if (deviceType != null && !deviceType.isBlank()) {
|
||||
qw.eq("type", deviceType);
|
||||
}
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
qw.in("device_id", ids);
|
||||
}
|
||||
List<Device> list = this.list(qw);
|
||||
return buildExportWorkbookBytes(deviceType, list);
|
||||
} catch (Exception e) {
|
||||
log.error("device export v2 failed", e);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] templateDevicesV2(String deviceType) {
|
||||
try {
|
||||
if (deviceType == null || deviceType.isBlank()) return new byte[0];
|
||||
return buildTemplateWorkbookBytes(deviceType);
|
||||
} catch (Exception e) {
|
||||
log.error("device template v2 failed", e);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createDevice(Device device) {
|
||||
|
||||
@ -162,6 +228,358 @@ public class DeviceServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> validateExcelV2(Workbook workbook, String projectId, String deviceType) {
|
||||
try (Workbook wb = workbook) {
|
||||
// 1)初始化与基础判空
|
||||
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
|
||||
evaluator.evaluateAll();
|
||||
DataFormatter formatter = new DataFormatter();
|
||||
Sheet sheet = wb.getSheetAt(0);
|
||||
if (sheet == null) return Map.of("ok", false, "msg", "sheet为空", "errors", List.of());
|
||||
Row headerRow = sheet.getRow(0);
|
||||
if (headerRow == null) return Map.of("ok", false, "msg", "表头为空", "errors", List.of());
|
||||
|
||||
// 2)读取表头,建立列名到列下标的映射
|
||||
Map<String, Integer> idx = new HashMap<>();
|
||||
for (int i = 0; i < headerRow.getLastCellNum(); i++) {
|
||||
Cell c = headerRow.getCell(i);
|
||||
if (c == null) continue;
|
||||
String key = c.getStringCellValue();
|
||||
if (key != null) idx.put(key.trim().toLowerCase(), i);
|
||||
}
|
||||
// 3)设备类型 deviceType / type 列 的强制规则
|
||||
List<Map<String, Object>> errors = new ArrayList<>();
|
||||
List<Device> rows = new ArrayList<>();
|
||||
|
||||
boolean hasTypeCol = idx.containsKey("type") || idx.containsKey("device_type");
|
||||
if ((deviceType == null || deviceType.isBlank()) && !hasTypeCol) {
|
||||
return Map.of("ok", false, "msg", "deviceType 必填(或在表里提供 type/device_type 列)", "errors", List.of());
|
||||
}
|
||||
|
||||
// 4)size 来源规则:优先用 size(JSON) 列;否则按 deviceType 推导尺寸列
|
||||
boolean hasSizeJson = idx.containsKey("size");
|
||||
if (!hasSizeJson && (deviceType == null || deviceType.isBlank())) {
|
||||
return Map.of("ok", false, "msg", "缺少 size 列且无法按 deviceType 推导尺寸列", "errors", List.of());
|
||||
}
|
||||
|
||||
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null) continue;
|
||||
|
||||
String rowType = deviceType;
|
||||
if (rowType == null || rowType.isBlank()) {
|
||||
Integer ti = idx.get("type");
|
||||
if (ti == null) ti = idx.get("device_type");
|
||||
rowType = ti == null ? null : cleanString(getString(row, ti));
|
||||
}
|
||||
if (rowType == null || rowType.isBlank()) {
|
||||
errors.add(err(r, "缺少 type/device_type"));
|
||||
continue;
|
||||
}
|
||||
|
||||
String code = idx.containsKey("code") ? cleanString(getString(row, idx.get("code"))) : null;
|
||||
String name = idx.containsKey("name") ? cleanString(getString(row, idx.get("name"))) : null;
|
||||
if ((code == null || code.isBlank()) && (name == null || name.isBlank())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String sizeJson = null;
|
||||
if (hasSizeJson) {
|
||||
String raw = cleanString(getString(row, idx.get("size")));
|
||||
if (raw != null) {
|
||||
sizeJson = validateJson(raw);
|
||||
if (sizeJson == null) {
|
||||
errors.add(err(r, "size 非法JSON"));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sizeJson == null) {
|
||||
List<String> sizeCols = getSizeColumnsByDeviceType(rowType);
|
||||
if (sizeCols.isEmpty()) {
|
||||
errors.add(err(r, "未知设备类型: " + rowType));
|
||||
continue;
|
||||
}
|
||||
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 (m.isEmpty()) {
|
||||
errors.add(err(r, "尺寸列为空"));
|
||||
continue;
|
||||
}
|
||||
sizeJson = objectMapper.writeValueAsString(m);
|
||||
}
|
||||
|
||||
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.setCreatedAt(LocalDateTime.now());
|
||||
d.setUpdatedAt(LocalDateTime.now());
|
||||
d.setModifier(currentUsername());
|
||||
rows.add(d);
|
||||
}
|
||||
|
||||
Map<String, Object> out = new HashMap<>();
|
||||
out.put("ok", !rows.isEmpty());
|
||||
out.put("msg", errors.isEmpty() ? "OK" : ("存在错误: " + errors.size()));
|
||||
out.put("totalRows", sheet.getLastRowNum());
|
||||
out.put("validRows", rows.size());
|
||||
out.put("errorRows", errors.size());
|
||||
out.put("errors", errors.size() > 50 ? errors.subList(0, 50) : errors);
|
||||
out.put("_rows", rows);
|
||||
return out;
|
||||
} catch (Exception e) {
|
||||
return Map.of("ok", false, "msg", "解析失败: " + e.getMessage(), "errors", List.of());
|
||||
}
|
||||
}
|
||||
|
||||
private Double getDoubleFlexible(Row row, int i, FormulaEvaluator evaluator, DataFormatter formatter) {
|
||||
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 {
|
||||
return Double.parseDouble(t);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String cleanString(String s) {
|
||||
if (s == null) return null;
|
||||
String t = s.trim();
|
||||
return t.isEmpty() ? null : t;
|
||||
}
|
||||
|
||||
private Map<String, Object> err(int row, String msg) {
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("row", row);
|
||||
m.put("msg", msg);
|
||||
return m;
|
||||
}
|
||||
|
||||
private List<String> getSizeColumnsByDeviceType(String deviceType) {
|
||||
if (deviceType == null || deviceType.isBlank()) return Collections.emptyList();
|
||||
try {
|
||||
List<String> keys = deviceSizeSchemaRegistry.getSizeKeys(deviceType);
|
||||
return keys == null ? Collections.emptyList() : keys;
|
||||
} catch (Exception e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getAllSizeColumns() {
|
||||
try {
|
||||
java.util.LinkedHashSet<String> set = new java.util.LinkedHashSet<>();
|
||||
Map<String, com.yfd.business.css.meta.DeviceSizeSchema> all = deviceSizeSchemaRegistry.getAllSchemas();
|
||||
if (all != null) {
|
||||
for (com.yfd.business.css.meta.DeviceSizeSchema s : all.values()) {
|
||||
if (s == null || s.getFields() == null) continue;
|
||||
for (com.yfd.business.css.meta.DeviceSizeField f : s.getFields()) {
|
||||
if (f != null && f.getKey() != null && !f.getKey().isBlank()) {
|
||||
set.add(f.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(set);
|
||||
} catch (Exception e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] buildTemplateWorkbookBytes(String deviceType) throws Exception {
|
||||
List<String> sizeCols = getSizeColumnsByDeviceType(deviceType);
|
||||
if (sizeCols.isEmpty()) sizeCols = getAllSizeColumns();
|
||||
List<String> headers = new ArrayList<>();
|
||||
headers.add("type");
|
||||
headers.add("project_id");
|
||||
headers.add("code");
|
||||
headers.add("name");
|
||||
headers.addAll(sizeCols);
|
||||
headers.add("size");
|
||||
headers.add("volume");
|
||||
headers.add("flow_rate");
|
||||
headers.add("pulse_velocity");
|
||||
try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
||||
Sheet sheet = wb.createSheet("devices");
|
||||
Row hr = sheet.createRow(0);
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
hr.createCell(i).setCellValue(headers.get(i));
|
||||
}
|
||||
Row r1 = sheet.createRow(1);
|
||||
r1.createCell(0).setCellValue(deviceType);
|
||||
r1.createCell(1).setCellValue("-1");
|
||||
wb.write(bos);
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] buildExportWorkbookBytes(String deviceType, List<Device> list) throws Exception {
|
||||
List<String> sizeCols = (deviceType == null || deviceType.isBlank()) ? getAllSizeColumns() : getSizeColumnsByDeviceType(deviceType);
|
||||
if (sizeCols.isEmpty()) sizeCols = getAllSizeColumns();
|
||||
List<String> headers = new ArrayList<>();
|
||||
// headers.add("device_id");
|
||||
// headers.add("project_id");
|
||||
headers.add("type");
|
||||
headers.add("code");
|
||||
headers.add("name");
|
||||
headers.addAll(sizeCols);
|
||||
// headers.add("size");
|
||||
headers.add("volume");
|
||||
headers.add("flow_rate");
|
||||
headers.add("pulse_velocity");
|
||||
// headers.add("created_at");
|
||||
// headers.add("updated_at");
|
||||
// headers.add("modifier");
|
||||
|
||||
try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
||||
Sheet sheet = wb.createSheet("devices");
|
||||
Row hr = sheet.createRow(0);
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
hr.createCell(i).setCellValue(headers.get(i));
|
||||
}
|
||||
int rowNum = 1;
|
||||
for (Device d : list) {
|
||||
Row r = sheet.createRow(rowNum++);
|
||||
int col = 0;
|
||||
// r.createCell(col++).setCellValue(nvl(d.getDeviceId()));
|
||||
// r.createCell(col++).setCellValue(nvl(d.getProjectId()));
|
||||
r.createCell(col++).setCellValue(nvl(d.getType()));
|
||||
r.createCell(col++).setCellValue(nvl(d.getCode()));
|
||||
r.createCell(col++).setCellValue(nvl(d.getName()));
|
||||
|
||||
Map<String, String> flat = flattenSize(d.getType(), d.getSize(), sizeCols);
|
||||
for (String k : sizeCols) {
|
||||
r.createCell(col++).setCellValue(nvl(flat.get(k)));
|
||||
}
|
||||
// r.createCell(col++).setCellValue(nvl(d.getSize()));
|
||||
r.createCell(col++).setCellValue(d.getVolume() == null ? "" : d.getVolume().toString());
|
||||
r.createCell(col++).setCellValue(d.getFlowRate() == null ? "" : d.getFlowRate().toString());
|
||||
r.createCell(col++).setCellValue(d.getPulseVelocity() == null ? "" : d.getPulseVelocity().toString());
|
||||
// r.createCell(col++).setCellValue(d.getCreatedAt() == null ? "" : d.getCreatedAt().toString());
|
||||
// r.createCell(col++).setCellValue(d.getUpdatedAt() == null ? "" : d.getUpdatedAt().toString());
|
||||
// r.createCell(col++).setCellValue(nvl(d.getModifier()));
|
||||
}
|
||||
wb.write(bos);
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> flattenSize(String deviceType, String sizeJson, List<String> cols) {
|
||||
Map<String, String> out = new HashMap<>();
|
||||
if (cols == null || cols.isEmpty()) return out;
|
||||
if (sizeJson == null || sizeJson.isBlank()) return out;
|
||||
try {
|
||||
JsonNode root = objectMapper.readTree(sizeJson);
|
||||
if ("ExtractionColumn".equals(deviceType)) {
|
||||
pull(root, out, "upper_diameter", "upper_height", "tray_diameter", "tray_height", "lower_diameter", "lower_height");
|
||||
if (!out.isEmpty()) return out;
|
||||
JsonNode up = root.get("upper_expanded");
|
||||
JsonNode tray = root.get("tray_section");
|
||||
JsonNode low = root.get("lower_expanded");
|
||||
if (up != null) {
|
||||
putIfNum(out, "upper_diameter", up.get("diameter"));
|
||||
putIfNum(out, "upper_height", up.get("height"));
|
||||
}
|
||||
if (tray != null) {
|
||||
putIfNum(out, "tray_diameter", tray.get("diameter"));
|
||||
putIfNum(out, "tray_height", tray.get("height"));
|
||||
}
|
||||
if (low != null) {
|
||||
putIfNum(out, "lower_diameter", low.get("diameter"));
|
||||
putIfNum(out, "lower_height", low.get("height"));
|
||||
}
|
||||
} else if ("FluidizedBed".equals(deviceType)) {
|
||||
pull(root, out, "expanded_diameter", "expanded_height", "transition_height", "reaction_diameter", "reaction_height");
|
||||
if (!out.isEmpty()) return out;
|
||||
JsonNode ex = root.get("expanded_section");
|
||||
JsonNode tr = root.get("transition_section");
|
||||
JsonNode re = root.get("reaction_section");
|
||||
if (ex != null) {
|
||||
putIfNum(out, "expanded_diameter", ex.get("diameter"));
|
||||
putIfNum(out, "expanded_height", ex.get("height"));
|
||||
}
|
||||
if (tr != null) {
|
||||
putIfNum(out, "transition_height", tr.get("height"));
|
||||
}
|
||||
if (re != null) {
|
||||
putIfNum(out, "reaction_diameter", re.get("diameter"));
|
||||
putIfNum(out, "reaction_height", re.get("height"));
|
||||
}
|
||||
} else if ("ACFTank".equals(deviceType)) {
|
||||
pull(root, out, "cylinder_diameter", "cylinder_height", "bottom_diameter", "bottom_height", "scab_thickness", "scab_height");
|
||||
if (!out.isEmpty()) return out;
|
||||
JsonNode cyl = root.get("annular_cylinder");
|
||||
if (cyl != null) {
|
||||
JsonNode od = cyl.get("outer_diameter");
|
||||
if (od == null) od = cyl.get("diameter");
|
||||
putIfNum(out, "cylinder_diameter", od);
|
||||
putIfNum(out, "cylinder_height", cyl.get("height"));
|
||||
}
|
||||
JsonNode bottom = root.get("frustum_bottom");
|
||||
if (bottom != null) {
|
||||
putIfNum(out, "bottom_diameter", bottom.get("bottom_diameter"));
|
||||
putIfNum(out, "bottom_height", bottom.get("height"));
|
||||
}
|
||||
JsonNode scab = root.get("scab");
|
||||
if (scab != null) {
|
||||
putIfNum(out, "scab_thickness", scab.get("thickness"));
|
||||
putIfNum(out, "scab_height", scab.get("height"));
|
||||
}
|
||||
} else {
|
||||
pull(root, out, "length", "width", "height", "diameter");
|
||||
if (!out.containsKey("diameter")) {
|
||||
JsonNode od = root.get("outer_diameter");
|
||||
if (od != null) putIfNum(out, "diameter", od);
|
||||
}
|
||||
}
|
||||
for (String k : cols) {
|
||||
if (out.containsKey(k)) continue;
|
||||
JsonNode v = root.get(k);
|
||||
if (v != null) {
|
||||
if (v.isNumber()) out.put(k, v.numberValue().toString());
|
||||
else if (v.isTextual()) out.put(k, v.asText());
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private void pull(JsonNode root, Map<String, String> out, String... keys) {
|
||||
for (String k : keys) {
|
||||
JsonNode v = root.get(k);
|
||||
if (v == null || v.isNull()) continue;
|
||||
if (v.isNumber()) out.put(k, v.numberValue().toString());
|
||||
else if (v.isTextual()) out.put(k, v.asText());
|
||||
}
|
||||
}
|
||||
|
||||
private void putIfNum(Map<String, String> out, String key, JsonNode v) {
|
||||
if (v == null || v.isNull()) return;
|
||||
if (v.isNumber()) out.put(key, v.numberValue().toString());
|
||||
else if (v.isTextual()) out.put(key, v.asText());
|
||||
}
|
||||
|
||||
private String nvl(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
private String getString(Row row, int i) {
|
||||
Cell c = row.getCell(i);
|
||||
if (c == null) return null;
|
||||
|
||||
@ -17,6 +17,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import jakarta.annotation.Resource;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
@ -26,6 +27,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
@ -99,6 +101,36 @@ public class MaterialServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] exportMaterialsV2(String projectId, List<String> ids, String nameLike) {
|
||||
try {
|
||||
QueryWrapper<Material> qw = new QueryWrapper<>();
|
||||
qw.eq("project_id", projectId);
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
qw.in("material_id", ids);
|
||||
}
|
||||
if (nameLike != null && !nameLike.isBlank()) {
|
||||
qw.like("name", nameLike);
|
||||
}
|
||||
qw.orderByDesc("created_at");
|
||||
List<Material> list = this.list(qw);
|
||||
return buildExportWorkbookBytes(list);
|
||||
} catch (Exception e) {
|
||||
log.error("materials export v2 failed", e);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] templateMaterialsV2() {
|
||||
try {
|
||||
return buildTemplateWorkbookBytes();
|
||||
} catch (Exception e) {
|
||||
log.error("materials template v2 failed", e);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
private boolean importExcel(Workbook workbook) {
|
||||
try (Workbook wb = workbook) {
|
||||
Sheet sheet = wb.getSheetAt(0);
|
||||
@ -121,7 +153,7 @@ public class MaterialServiceImpl
|
||||
log.info("material excel header keys={}", idx.keySet());
|
||||
String[] keys = new String[]{
|
||||
"name","u_concentration","uo2_density","u_enrichment",
|
||||
"pu_concentration","puo2_density","pu_isotope",
|
||||
"pu_concentration","puo2_density","e_pu240","e_pu242","e_pu241","e_pu239","e_pu238",
|
||||
"hno3_acidity","h2c2o4_concentration","organic_ratio",
|
||||
"moisture_content","custom_attrs"
|
||||
};
|
||||
@ -144,7 +176,12 @@ public class MaterialServiceImpl
|
||||
m.setUEnrichment(cleanDecimal(getDecimal(row, idx.get("u_enrichment"))));
|
||||
m.setPuConcentration(cleanDecimal(getDecimal(row, idx.get("pu_concentration"))));
|
||||
m.setPuo2Density(cleanDecimal(getDecimal(row, idx.get("puo2_density"))));
|
||||
m.setPuIsotope(cleanDecimal(getDecimal(row, idx.get("pu_isotope"))));
|
||||
// System.out.println("e_pu240="+getDecimal(row, idx.get("e_pu240")));
|
||||
m.setEPu240(cleanDecimal(getDecimal(row, idx.get("e_pu240"))));
|
||||
m.setEPu242(cleanDecimal(getDecimal(row, idx.get("e_pu242"))));
|
||||
m.setEPu241(cleanDecimal(getDecimal(row, idx.get("e_pu241"))));
|
||||
m.setEPu239(cleanDecimal(getDecimal(row, idx.get("e_pu239"))));
|
||||
m.setEPu238(cleanDecimal(getDecimal(row, idx.get("e_pu238"))));
|
||||
m.setHno3Acidity(cleanDecimal(getDecimal(row, idx.get("hno3_acidity"))));
|
||||
m.setH2c2o4Concentration(cleanDecimal(getDecimal(row, idx.get("h2c2o4_concentration"))));
|
||||
m.setOrganicRatio(cleanDecimal(getDecimal(row, idx.get("organic_ratio"))));
|
||||
@ -244,4 +281,92 @@ public class MaterialServiceImpl
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] buildTemplateWorkbookBytes() throws Exception {
|
||||
List<String> headers = materialExportHeaders();
|
||||
try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
||||
Sheet sheet = wb.createSheet("materials");
|
||||
Row hr = sheet.createRow(0);
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
hr.createCell(i).setCellValue(headers.get(i));
|
||||
}
|
||||
wb.write(bos);
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] buildExportWorkbookBytes(List<Material> list) throws Exception {
|
||||
List<String> headers = materialExportHeaders();
|
||||
try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
||||
Sheet sheet = wb.createSheet("materials");
|
||||
Row hr = sheet.createRow(0);
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
hr.createCell(i).setCellValue(headers.get(i));
|
||||
}
|
||||
int rowNum = 1;
|
||||
for (Material m : list) {
|
||||
Row r = sheet.createRow(rowNum++);
|
||||
int col = 0;
|
||||
// r.createCell(col++).setCellValue(nvl(m.getMaterialId()));
|
||||
// r.createCell(col++).setCellValue(nvl(m.getProjectId()));
|
||||
r.createCell(col++).setCellValue(nvl(m.getName()));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getUConcentration())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getUEnrichment())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getUo2Density())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getPuConcentration())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getPuo2Density())));
|
||||
// r.createCell(col++).setCellValue(nvl(toStr(m.getPuIsotope())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getEPu240())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getEPu241())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getEPu242())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getEPu239())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getEPu238())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getHno3Acidity())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getH2c2o4Concentration())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getOrganicRatio())));
|
||||
r.createCell(col++).setCellValue(nvl(toStr(m.getMoistureContent())));
|
||||
r.createCell(col++).setCellValue(nvl(m.getCustomAttrs()));
|
||||
// r.createCell(col++).setCellValue(m.getCreatedAt() == null ? "" : m.getCreatedAt().toString());
|
||||
// r.createCell(col++).setCellValue(m.getUpdatedAt() == null ? "" : m.getUpdatedAt().toString());
|
||||
// r.createCell(col++).setCellValue(nvl(m.getModifier()));
|
||||
}
|
||||
wb.write(bos);
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> materialExportHeaders() {
|
||||
List<String> headers = new ArrayList<>();
|
||||
// headers.add("material_id");
|
||||
// headers.add("project_id");
|
||||
headers.add("name");
|
||||
headers.add("u_concentration");
|
||||
headers.add("u_enrichment");
|
||||
headers.add("uo2_density");
|
||||
headers.add("pu_concentration");
|
||||
headers.add("puo2_density");
|
||||
// headers.add("pu_isotope");
|
||||
headers.add("e_pu240");
|
||||
headers.add("e_pu241");
|
||||
headers.add("e_pu242");
|
||||
headers.add("e_pu239");
|
||||
headers.add("e_pu238");
|
||||
headers.add("hno3_acidity");
|
||||
headers.add("h2c2o4_concentration");
|
||||
headers.add("organic_ratio");
|
||||
headers.add("moisture_content");
|
||||
headers.add("custom_attrs");
|
||||
// headers.add("created_at");
|
||||
// headers.add("updated_at");
|
||||
// headers.add("modifier");
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String toStr(BigDecimal d) {
|
||||
return d == null ? null : d.stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
private String nvl(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
}
|
||||
|
||||
@ -683,14 +683,16 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
|
||||
throw new BizException("算法类型或设备类型不能为空");
|
||||
}
|
||||
|
||||
// 解析材料类型
|
||||
String materialType = "unknown";
|
||||
String outputPath = task.getModelOutputPath();
|
||||
if (outputPath != null && !outputPath.isBlank()) {
|
||||
String[] parts = outputPath.replace("\\", "/").split("/");
|
||||
// 预期结构: runs/{algorithmType}/{deviceType}/{materialType}/{taskId}/...
|
||||
if (parts.length > 4 && "runs".equals(parts[0])) {
|
||||
materialType = parts[3];
|
||||
String mt = normalizeMaterialType(parts[3]);
|
||||
if (mt != null) {
|
||||
materialType = mt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -768,6 +770,16 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
|
||||
return algorithmModelService.save(model);
|
||||
}
|
||||
|
||||
private String normalizeMaterialType(String raw) {
|
||||
if (raw == null) return null;
|
||||
String s = raw.trim();
|
||||
if (s.isEmpty()) return null;
|
||||
if ("U".equalsIgnoreCase(s)) return "U";
|
||||
if ("Pu".equalsIgnoreCase(s)) return "Pu";
|
||||
if ("Mixed".equalsIgnoreCase(s) || "MIX".equalsIgnoreCase(s)) return "Mixed";
|
||||
return s;
|
||||
}
|
||||
|
||||
private String firstNonBlank(String... values) {
|
||||
if (values == null) return null;
|
||||
for (String v : values) {
|
||||
|
||||
@ -811,11 +811,32 @@ public class ProjectServiceImpl
|
||||
state.put("diameter", diameter);
|
||||
}
|
||||
|
||||
if (sizeNode.has("tray_diameter")) {
|
||||
double diameter = sizeNode.get("tray_diameter").asDouble();
|
||||
state.put("diameter", diameter);
|
||||
}
|
||||
|
||||
if (sizeNode.has("tray_outer_diameter")) {
|
||||
double diameter = sizeNode.get("tray_outer_diameter").asDouble();
|
||||
state.put("diameter", diameter);
|
||||
}
|
||||
|
||||
// 读取 height
|
||||
if (sizeNode.has("height")) {
|
||||
double height = sizeNode.get("height").asDouble();
|
||||
state.put("height", height);
|
||||
}
|
||||
|
||||
if (sizeNode.has("tray_height")) {
|
||||
double height = sizeNode.get("tray_height").asDouble();
|
||||
state.put("height", height);
|
||||
} else if (sizeNode.has("upper_height")) {
|
||||
double height = sizeNode.get("upper_height").asDouble();
|
||||
state.put("height", height);
|
||||
} else if (sizeNode.has("lower_height")) {
|
||||
double height = sizeNode.get("lower_height").asDouble();
|
||||
state.put("height", height);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 出错可以记录日志
|
||||
|
||||
Loading…
Reference in New Issue
Block a user