fix:编辑报告保存图片和指标

This commit is contained in:
2026-02-11 16:30:50 +08:00
parent ad8cd33121
commit b9fc75396c
3 changed files with 246 additions and 61 deletions

View File

@@ -72,6 +72,12 @@ public class UploadFilesReq {
@Schema(description = "文件类型 1模型文件 2仿真报告 3计算文件 4曲线文件 5云图文件", implementation = FileBizTypeEnum.class)
private Integer fileType;
/**
* 之前的预留tag字段现在取tag1存储序号用来对生成任务报告里的图片排序
*/
@Schema(description = "排序号")
private String tag1;
// ----------------------------------------------------------------
// 很重要用于设置标签通过autoFillDictTags切面 设置dictTagIdsCache
/**

View File

@@ -444,7 +444,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
* @param fileSize 文件大小
* @return 创建后的文件元数据信息
*/
private FileMetadataInfo createFileMetadata(String filePath, String fileName, String projectId, String analysisDirectionId, String remarks, Long parentId, Long fileSize
private FileMetadataInfo createFileMetadata(String filePath, String fileName, String projectId, String analysisDirectionId, String remarks, Long parentId, Long fileSize, String tag1
) {
FileMetadataInfo fileInfo = new FileMetadataInfo();
fileInfo.setObjectKey(filePath);
@@ -459,6 +459,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
fileInfo.setCreatorId(ThreadLocalContext.getUserId());
fileInfo.setTenantId(ThreadLocalContext.getTenantId());
fileInfo.setFileSize(fileSize);
fileInfo.setTag1(tag1);
return fileInfo;
}
@@ -1604,7 +1605,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
minioService.copyFile(oldDirMinioObjectKey, newDirMinioObjectKey,sourceMetadataInfo.getBucketName());
// 创建目录元数据并保存到数据库
FileMetadataInfo fileInfo = createFileMetadata(newDirMinioObjectKey, sourceMetadataInfo.getOriginalName(),
null, null, null, targetParentMetadataInfo.getId(), sourceMetadataInfo.getFileSize());
null, null, null, targetParentMetadataInfo.getId(), sourceMetadataInfo.getFileSize(), sourceMetadataInfo.getTag1());
fileMetadataInfoService.save(fileInfo);
return SdmResponse.success("复制文件成功");
} catch (Exception e) {
@@ -1951,7 +1952,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
minioService.uploadFile(req.getFile(), fileMinioObjectKey, null, dirMetadataInfo.getBucketName());
FileMetadataInfo fileInfo = createFileMetadata(fileMinioObjectKey, originalName,
req.getProjectId(), req.getAnalysisDirectionId(), req.getRemarks(), dirMetadataInfo.getId(), req.getFile().getSize()
req.getProjectId(), req.getAnalysisDirectionId(), req.getRemarks(), dirMetadataInfo.getId(), req.getFile().getSize(), req.getTag1()
);
fileMetadataInfoService.save(fileInfo);
@@ -1990,7 +1991,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
try {
FileMetadataInfo fileInfo = createFileMetadata(fileMinioObjectKey, fileReq.getFileName(),
req.getProjectId(), req.getAnalysisDirectionId(), req.getRemarks(), dirMetadataInfo.getId(), fileReq.getSize());
req.getProjectId(), req.getAnalysisDirectionId(), req.getRemarks(), dirMetadataInfo.getId(), fileReq.getSize(), null);
fileInfo.setUploadTaskId(req.getUploadTaskId());
fileInfo.setTemplateId(req.getTemplateId());
fileInfo.setTemplateName(req.getTemplateName());
@@ -2600,7 +2601,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
minioService.uploadFile(req.getFile(), newFileMinioObjectKey, null,oldFileMetadataInfo.getBucketName());
// 创建目录元数据并保存到数据库
FileMetadataInfo newFileInfo = createFileMetadata(newFileMinioObjectKey, req.getFileName(),
req.getProjectId(), req.getAnalysisDirectionId(), req.getRemarks(), oldFileMetadataInfo.getParentId(), req.getFile().getSize()
req.getProjectId(), req.getAnalysisDirectionId(), req.getRemarks(), oldFileMetadataInfo.getParentId(), req.getFile().getSize(), null
);
newFileInfo.setRemarks(oldFileMetadataInfo.getRemarks());
newFileInfo.setProjectId(oldFileMetadataInfo.getProjectId());
@@ -2792,7 +2793,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
// 创建目录元数据并保存到数据库
FileMetadataInfo fileInfo = createFileMetadata(avatarMinioObjectKey, newFilename,
null, null, null, parAvatarDirId, avatar.getSize());
null, null, null, parAvatarDirId, avatar.getSize(), null);
fileMetadataInfoService.save(fileInfo);
JSONObject jsonObject = new JSONObject();
jsonObject.put("avatarId", fileInfo.getId());
@@ -2853,7 +2854,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
// 创建目录元数据并保存到数据库
FileMetadataInfo fileInfo = createFileMetadata(simulationParamMinioObjectKey, originalFilename,
null, null, null, parSimulationParamDirId, paramFile.getSize());
null, null, null, parSimulationParamDirId, paramFile.getSize(), null);
// 设置文件状态为审批中 暂不可见
fileInfo.setApprovalStatus(ApprovalFileDataStatusEnum.PENDING.getKey());
fileInfo.setApproveType(ApproveFileDataTypeEnum.UPLOAD_REVIEWING.getCode());
@@ -2949,7 +2950,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
// 创建目录元数据并保存到数据库
FileMetadataInfo fileInfo = createFileMetadata(trainingMinioObjectKey, originalFilename,
null, null, null, parTrainModelDirId, trainModelFile.getSize());
null, null, null, parTrainModelDirId, trainModelFile.getSize(), null);
fileMetadataInfoService.save(fileInfo);
return SdmResponse.success(fileInfo.getId());
} catch (Exception e) {
@@ -2994,7 +2995,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
// 创建目录元数据并保存到数据库
FileMetadataInfo fileInfo = createFileMetadata(fileMinioObjectKey, originalFilename,
null, null, null, parDirId, file.getSize());
null, null, null, parDirId, file.getSize(), null);
fileMetadataInfoService.save(fileInfo);
return SdmResponse.success(fileInfo.getId());
} catch (Exception e) {

View File

@@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
@@ -43,6 +44,7 @@ import com.sdm.common.feign.impl.flowable.FlowableClientFeignClientImpl;
import com.sdm.common.feign.impl.system.ApproveFeignClientImpl;
import com.sdm.common.feign.impl.system.SysUserFeignClientImpl;
import com.sdm.common.service.DataFileService;
import com.sdm.common.utils.CommonUtils;
import com.sdm.common.utils.PageUtils;
import com.sdm.common.utils.RandomUtil;
import com.sdm.project.common.*;
@@ -73,6 +75,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -81,6 +84,7 @@ import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.sdm.common.service.BaseService.generateUuid;
@@ -1626,6 +1630,7 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
@Override
public SdmResponse editReport(EditReportReq req) {
Long userId = ThreadLocalContext.getUserId();
log.info("编辑报告参数为:{}", req);
String reportContent = req.getReportContent();
if (StringUtils.isNotEmpty(req.getTaskId())) {
@@ -1635,6 +1640,9 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
simulationTask.setReportTemplate(req.getReportTemplate());
simulationTask.setReportContent(reportContent);
simulationTaskService.updateById(simulationTask);
// 读取前端传入的指标表格数据,实现更新/替换/新增
archivePerformances(reportContent, simulationTask.getUuid(), null, userId);
}
} else {
SimulationRun simulationRun = this.lambdaQuery().eq(SimulationRun::getUuid, req.getRunId()).one();
@@ -1643,6 +1651,9 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
simulationRun.setReportTemplate(req.getReportTemplate());
simulationRun.setReportContent(reportContent);
this.updateById(simulationRun);
// 读取前端传入的指标表格数据,实现更新/替换/新增
archivePerformances(reportContent, simulationRun.getTaskId(), simulationRun.getUuid(), userId);
}
}
@@ -1714,66 +1725,233 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
} else {
log.info(commands + "执行脚本完成!");
}
byte[] fileData = null;
try {
// 获取临时路径中脚本生成的报告
String reportName = "report_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +
".docx";
FileInputStream fileInputStream = new FileInputStream(TEMP_REPORT_PATH + randomId + File.separator + reportName);
fileData = fileInputStream.readAllBytes();
// 创建临时MultipartFile
MockMultipartFile multipartFile = new MockMultipartFile(
reportName,
reportName,
"application/octet-stream",
fileData);
if (StringUtils.isNotEmpty(req.getTaskId())) {
// 上传到任务下的交付物文件夹的报告文件夹下
Long parentId = getParentDirId(req.getTaskId(), FileBizTypeEnum.REPORT_FILE.getDirName());
UploadFilesReq filesReq = new UploadFilesReq();
filesReq.setFile(multipartFile);
filesReq.setFileName(reportName);
filesReq.setFileType(FileBizTypeEnum.REPORT_FILE.getValue());
filesReq.setUuid(null);
filesReq.setDirId(parentId);
filesReq.setFileTypeDictClass(FileDictTagEnum.FILE_TYPE.getDictClass());
filesReq.setFileTypeDictValue(String.valueOf(FileBizTypeEnum.REPORT_FILE.getValue()));
filesReq.setDictTags(Arrays.asList(FileDictTagEnum.FILE_TYPE.getDictClassFieldName(), FileDictTagEnum.FILE_TYPE.getDictValueFieldName()));
SdmResponse sdmResponse = uploadKeyResultFiles(filesReq);
if (!sdmResponse.isSuccess()) {
throw new RuntimeException("生成自动化报告上传任务报告结果目录失败");
}
} else {
// 上传到算例下的报告文件夹下
KeyResultReq resultReq = new KeyResultReq();
resultReq.setKeyResultType(KeyResultTypeEnum.DOCUMENT.getKeyResultType());
resultReq.setRunId(req.getRunId());
resultReq.setName(reportName);
// 读取脚本生成的报告并归档
archiveReportAndImage(req.getTaskId(), req.getRunId(), randomId, FileBizTypeEnum.REPORT_FILE, null, null);
resultReq.setFile(multipartFile);
resultReq.setFileName(reportName);
resultReq.setFileType(FileBizTypeEnum.REPORT_FILE.getValue());
SdmResponse sdmResponse = addSimulationKeyResult(resultReq);
if (!sdmResponse.isSuccess()) {
throw new RuntimeException("生成自动化报告上传算例报告结果目录失败");
}
// 读取脚本生成的图片并按顺序归档
List<String> imageNames = extractPicNames(reportContent);
if (CollectionUtils.isNotEmpty(imageNames)) {
int sortOrder = 1;
for (String imageName : imageNames) {
archiveReportAndImage(req.getTaskId(), req.getRunId(), randomId, FileBizTypeEnum.CLOUD_FILE, imageName, String.valueOf(sortOrder++));
}
fileInputStream.close();
// 删除临时路径
log.info("删除临时路径:{},中。。。。。。", randomId);
deleteFolder(new File(TEMP_REPORT_PATH + randomId));
return SdmResponse.success();
} catch (Exception ex) {
log.error("生成自动化报告失败:{}", ex.getMessage());
throw new RuntimeException("生成自动化报告失败");
}
// 删除临时路径
// log.info("删除临时路径:{},中。。。。。。", randomId);
// deleteFolder(new File(TEMP_REPORT_PATH + randomId));
}
return SdmResponse.failed("生成自动化报告失败");
}
private void archiveReportAndImage(String taskId, String runId, String randomId, FileBizTypeEnum fileBizTypeEnum, String imageName, String sortOrder) {
byte[] fileData = null;
try {
String fileName = "";
// 获取临时路径中脚本生成的报告
if (fileBizTypeEnum == FileBizTypeEnum.CLOUD_FILE) {
fileName = imageName + ".png";
} else {
fileName = "report_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +
".docx";
}
FileInputStream fileInputStream = new FileInputStream(TEMP_REPORT_PATH + randomId + File.separator + fileName);
fileData = fileInputStream.readAllBytes();
// 创建临时MultipartFile
MockMultipartFile multipartFile = new MockMultipartFile(
fileName,
fileName,
"application/octet-stream",
fileData);
if (StringUtils.isNotEmpty(taskId)) {
// 上传到任务下的交付物文件夹的报告文件夹下
Long parentId = getParentDirId(taskId, fileBizTypeEnum.getDirName());
UploadFilesReq filesReq = new UploadFilesReq();
filesReq.setFile(multipartFile);
filesReq.setFileName(fileName);
filesReq.setFileType(fileBizTypeEnum.getValue());
filesReq.setUuid(null);
filesReq.setDirId(parentId);
filesReq.setTag1(sortOrder);
filesReq.setFileTypeDictClass(FileDictTagEnum.FILE_TYPE.getDictClass());
filesReq.setFileTypeDictValue(String.valueOf(fileBizTypeEnum.getValue()));
filesReq.setDictTags(Arrays.asList(FileDictTagEnum.FILE_TYPE.getDictClassFieldName(), FileDictTagEnum.FILE_TYPE.getDictValueFieldName()));
SdmResponse sdmResponse = uploadKeyResultFiles(filesReq);
if (!sdmResponse.isSuccess()) {
throw new RuntimeException("生成自动化报告上传任务报告结果目录失败");
}
} else {
// 上传到算例下的报告文件夹下
KeyResultReq resultReq = new KeyResultReq();
resultReq.setKeyResultType(fileBizTypeEnum == FileBizTypeEnum.CLOUD_FILE ? KeyResultTypeEnum.IMAGE.getKeyResultType() : KeyResultTypeEnum.DOCUMENT.getKeyResultType());
resultReq.setRunId(runId);
resultReq.setName(fileName);
resultReq.setFile(multipartFile);
resultReq.setFileName(fileName);
resultReq.setFileType(fileBizTypeEnum.getValue());
SdmResponse sdmResponse = addSimulationKeyResult(resultReq);
if (!sdmResponse.isSuccess()) {
throw new RuntimeException("生成自动化报告上传算例报告结果目录失败");
}
}
fileInputStream.close();
} catch (Exception ex) {
log.error("生成自动化报告失败:{}", ex.getMessage());
throw new RuntimeException("生成自动化报告失败");
}
}
private void archivePerformances(String reportContent, String taskId, String runId, Long userId) {
// 解析前端传入的指标
List<SimulationPerformance> performanceList = extractPerformance(reportContent);
List<SimulationPerformance> oldPerformances = null;
if (StringUtils.isNotEmpty(runId)) {
// 算例下的指标
oldPerformances = simulationPerformanceService.lambdaQuery().eq(SimulationPerformance::getRunId, runId).list();
} else {
// 任务下的指标
oldPerformances = simulationPerformanceService.lambdaQuery().eq(SimulationPerformance::getTaskId, taskId).isNull(SimulationPerformance::getRunId).list();
}
if (CollectionUtils.isEmpty(oldPerformances)) {
// 直接新增
for (SimulationPerformance newPerformance : performanceList) {
newPerformance.setTaskId(taskId);
newPerformance.setRunId(runId);
newPerformance.setUuid(RandomUtil.generateString(32));
newPerformance.setCompleteStatus(getCompleteStatus(newPerformance));
newPerformance.setCreator(userId);
}
} else {
Map<String, List<SimulationPerformance>> oldPerformanceMap = oldPerformances.stream().collect(Collectors.groupingBy(SimulationPerformance::getNodeName));
for (SimulationPerformance newPerformance : performanceList) {
List<SimulationPerformance> oldPerformanceByNodeName = oldPerformanceMap.get(newPerformance.getNodeName());
if (CollectionUtils.isNotEmpty(oldPerformanceByNodeName)) {
newPerformance.setId(oldPerformanceByNodeName.get(0).getId());
newPerformance.setCompleteStatus(getCompleteStatus(newPerformance));
} else {
newPerformance.setTaskId(taskId);
newPerformance.setRunId(runId);
newPerformance.setUuid(RandomUtil.generateString(32));
newPerformance.setCompleteStatus(getCompleteStatus(newPerformance));
newPerformance.setCreator(userId);
}
}
}
List<SimulationPerformance> updatePerformances = performanceList.stream().filter(i -> i.getId() != null).toList();
List<SimulationPerformance> addPerformances = performanceList.stream().filter(i -> i.getId() == null).toList();
simulationPerformanceService.updateBatchById(updatePerformances);
simulationPerformanceService.saveBatch(addPerformances);
}
/**
* 读取前端传入的JSON参数获得图片名称以便获取到python脚本生成的图片文件
*/
public List<String> extractPicNames(String jsonData) {
List<String> imageNames = new ArrayList<>();
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode rootNode = objectMapper.readTree(jsonData);
for (JsonNode node : rootNode) {
// 检查是否为图片类型
if (node.has("type") && "img".equals(node.get("type").asText())) {
JsonNode valueNode = node.get("value");
if (valueNode != null && valueNode.isArray()) {
// 遍历数组获取所有图片路径
for (JsonNode imageNode : valueNode) {
if (imageNode.has("picName")) {
imageNames.add(imageNode.get("picName").asText());
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return imageNames;
}
/**
* 读取前端传入的JSON参数获得指标数据
*/
public List<SimulationPerformance> extractPerformance(String jsonData) {
List<SimulationPerformance> performances = new ArrayList<>();
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode rootNode = objectMapper.readTree(jsonData);
for (JsonNode node : rootNode) {
// 检查是否为图片类型
JsonNode paramsNode = node.get("params");
if (paramsNode != null && paramsNode.has("tableType") && "performance".equals(paramsNode.get("tableType").asText())) {
JsonNode valueNode = node.get("value");
if (valueNode != null && valueNode.isArray()) {
performances.addAll(objectMapper.convertValue(valueNode, new TypeReference<List<SimulationPerformance>>() {}));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 去重
performances = new ArrayList<>(performances.stream()
.collect(Collectors.toMap(
SimulationPerformance::getNodeName, // keynodeName
Function.identity(), // value对象本身
(existing, replacement) -> existing // 冲突时保留已有的(第一个)
))
.values());
return performances;
}
public String getCompleteStatus(SimulationPerformance simulationPerformance) {
// 目标值
String targetValueStr = simulationPerformance.getTargetValue();
// 分析值
String resultValueStr = simulationPerformance.getResultValue();
// 达标方式
String method = simulationPerformance.getMethod();
// 校验
if (StringUtils.isNotBlank(targetValueStr) && StringUtils.isNotBlank(resultValueStr)) {
if (!validateNumber(targetValueStr) || !validateNumber(resultValueStr)) {
return null;
}
try {
// 转换为BigDecimal自动处理整数和小数
BigDecimal targetValue = new BigDecimal(targetValueStr);
BigDecimal resultValue = new BigDecimal(resultValueStr);
int comparison = resultValue.compareTo(targetValue);
boolean isPassed = switch (method) {
case "" -> comparison >= 0;
case ">" -> comparison > 0;
case "<" -> comparison < 0;
case "" -> comparison <= 0;
default -> throw new IllegalArgumentException("无效运算符: " + method);
};
return isPassed ? PerformanceStatusEnum.STARTED.getCode() : PerformanceStatusEnum.NOT_STARTED.getCode();
} catch (Exception e) {
log.error("数字格式异常:", e);
}
}
return null;
}
private boolean validateNumber(String input) {
String trimmed = input.trim();
// 前置格式检查 - 验证是不是有效数字格式
if (!CommonUtils.isValidNumberFormat(trimmed)) {
return false;
}
return true;
}
@Override
public void editReportAndDownload(EditReportReq req, HttpServletResponse response) {
log.info("编辑报告参数为:{}", req);