This commit is contained in:
2026-03-20 10:53:49 +08:00
3 changed files with 202 additions and 212 deletions

View File

@@ -525,7 +525,7 @@ public class DataFileController implements IDataFeignClient {
@GetMapping("/getMinioPresignedUrl")
@Operation(summary = "获取MinIO文件下载的预签名URL", description = "获取MinIO文件的预签名URL")
public SdmResponse<MinioDownloadUrlResp> getMinioPresignedUrl(@Parameter(description = "文件id") @RequestParam("fileId") Long fileId, @RequestParam("skipPermissionCheck") Boolean skipPermissionCheck) {
public SdmResponse<MinioDownloadUrlResp> getMinioPresignedUrl(@Parameter(description = "文件id") @RequestParam("fileId") Long fileId, @RequestParam(value = "skipPermissionCheck", required = false, defaultValue = "false") Boolean skipPermissionCheck) {
return IDataFileService.getDownloadUrlWithPermission(fileId, skipPermissionCheck);
}
@@ -608,7 +608,7 @@ public class DataFileController implements IDataFeignClient {
*/
@GetMapping("/downloadFileForEdit")
@Operation(summary = "根据fileId下载文件到指定目录并返回该文件的系统路径", description = "根据fileId下载文件到指定目录并返回该文件的系统路径")
public SdmResponse downloadFileForEdit(@RequestParam(value = "fileId") @Validated Long fileId,@RequestParam("skipPermissionCheck") Boolean skipPermissionCheck) {
public SdmResponse downloadFileForEdit(@RequestParam(value = "fileId") @Validated Long fileId,@RequestParam(value = "skipPermissionCheck", required = false, defaultValue = "false") Boolean skipPermissionCheck) {
return IDataFileService.downloadFileForEdit(fileId,skipPermissionCheck);
}

View File

@@ -87,6 +87,7 @@ import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -1832,126 +1833,218 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
@Override
public SdmResponse editReport(EditReportReq req) {
Long userId = ThreadLocalContext.getUserId();
log.info("编辑报告参数为:{}", req);
Long userId = ThreadLocalContext.getUserId();
String reportContent = req.getReportContent();
// 1. 更新报告绑定,获取任务名称
String taskName = updateReportBinding(req, userId);
// 2. 创建临时文件夹
String randomId = createTempReportFolder();
log.info("临时路径为:{}", randomId);
// 3. 准备报告模板
String originalName = prepareReportTemplate(req.getReportTemplate(), randomId);
if (originalName == null) {
return SdmResponse.failed("获取报告模板失败");
}
// 4. 生成报告名称(使用任务名称作为前缀)
String reportName = generateReportName(taskName);
// 5. 构建并执行Python脚本
String commands = buildModifyReportCommand(randomId, originalName, reportName);
writeReportContentJson(randomId, reportContent);
SdmResponse executeResult = executePythonScriptWithTimeout(commands);
if (!executeResult.isSuccess()) {
return SdmResponse.failed(executeResult.getMessage());
}
// 6. 归档报告和图片
archiveGeneratedReport(reportName, req.getTaskId(), req.getRunId(), randomId, reportContent);
return SdmResponse.success();
}
// ==================== 报告编辑相关私有方法 ====================
/**
* 更新报告绑定(任务或算例),返回任务名称
*/
private String updateReportBinding(EditReportReq req, Long userId) {
String reportContent = req.getReportContent();
String taskName = "";
if (StringUtils.isNotEmpty(req.getTaskId())) {
SimulationTask simulationTask = simulationTaskService.lambdaQuery().eq(SimulationTask::getUuid, req.getTaskId()).one();
if (simulationTask != null) {
// 任务绑定报告模板
simulationTask.setReportTemplate(req.getReportTemplate());
simulationTask.setReportContent(reportContent);
simulationTaskService.updateById(simulationTask);
// 读取前端传入的指标表格数据,实现更新/替换/新增
archivePerformances(reportContent, simulationTask.getUuid(), null, userId);
taskName = simulationTask.getTaskName();
}
} else {
SimulationRun simulationRun = this.lambdaQuery().eq(SimulationRun::getUuid, req.getRunId()).one();
if (simulationRun != null) {
// 算例绑定报告模板
simulationRun.setReportTemplate(req.getReportTemplate());
simulationRun.setReportContent(reportContent);
this.updateById(simulationRun);
// 读取前端传入的指标表格数据,实现更新/替换/新增
String taskUuid = simulationRun.getTaskId();
SimulationTask task = simulationTaskService.lambdaQuery().eq(SimulationTask::getUuid, taskUuid).one();
if (task != null) {
taskName = task.getTaskName();
}
archivePerformances(reportContent, simulationRun.getTaskId(), simulationRun.getUuid(), userId);
}
}
return taskName;
}
/**
* 创建临时报告文件夹
*/
private String createTempReportFolder() {
String randomId = RandomUtil.generateString(16);
// 创建临时文件夹
Path folder = Paths.get(TEMP_REPORT_PATH + randomId);
if (!Files.exists(folder) || !Files.isDirectory(folder)) {
if (!new File(TEMP_REPORT_PATH + randomId).mkdir()) {
log.error("创建临时文件夹:{}失败",TEMP_REPORT_PATH + randomId);
log.error("创建临时文件夹:{}失败", TEMP_REPORT_PATH + randomId);
throw new RuntimeException("生成报告失败,原因为:创建临时文件夹失败");
}
}
log.info("临时路径为:{}", randomId);
return randomId;
}
// 根据文件id下载文件到临时目录
SdmResponse<ReportTemplateResp> reportResponse = reportFeignClient.queryReportTemplateInfo(req.getReportTemplate());
if (reportResponse.isSuccess()) {
Long reportTemplateFileId = reportResponse.getData().getFileId();
// 获取报告模板名称
GetFileBaseInfoReq getFileBaseInfoReq = new GetFileBaseInfoReq();
getFileBaseInfoReq.setFileId(reportTemplateFileId);
SdmResponse<FileMetadataInfoResp> fileBaseInfoResp = dataFeignClient.getFileBaseInfo(getFileBaseInfoReq);
String originalName = fileBaseInfoResp.getData().getOriginalName();
// 下载到本地临时目录
dataFeignClient.downloadFileToLocal(reportTemplateFileId, TEMP_REPORT_PATH + randomId);
/**
* 准备报告模板(获取模板信息并下载到临时目录)
*/
private String prepareReportTemplate(String reportTemplateId, String randomId) {
SdmResponse<ReportTemplateResp> reportResponse = reportFeignClient.queryReportTemplateInfo(reportTemplateId);
if (!reportResponse.isSuccess()) {
return null;
}
Long reportTemplateFileId = reportResponse.getData().getFileId();
GetFileBaseInfoReq getFileBaseInfoReq = new GetFileBaseInfoReq();
getFileBaseInfoReq.setFileId(reportTemplateFileId);
SdmResponse<FileMetadataInfoResp> fileBaseInfoResp = dataFeignClient.getFileBaseInfo(getFileBaseInfoReq);
String originalName = fileBaseInfoResp.getData().getOriginalName();
dataFeignClient.downloadFileToLocal(reportTemplateFileId, TEMP_REPORT_PATH + randomId);
return originalName;
}
String reportName = "report_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +
".docx";
/**
* 生成报告文件名
* @param taskName 任务名称,用作报告文件名前缀
*/
private String generateReportName(String taskName) {
String prefix = StringUtils.isNotEmpty(taskName) ? taskName : "report";
return prefix + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".docx";
}
// 构建python命令
List<String> command = new ArrayList<>();
command.add("python");
command.add("/opt/script/modifyReport.py");
// command.add(TEMP_REPORT_PATH + File.separator +"modifyReport.py");
command.add(TEMP_REPORT_PATH + randomId);
command.add(TEMP_REPORT_PATH + randomId + File.separator + originalName);
command.add(reportName);
String commands = String.join(" ", command);
/**
* 构建修改报告的Python命令
*/
private String buildModifyReportCommand(String randomId, String originalName, String reportName) {
List<String> command = new ArrayList<>();
command.add("python");
command.add("/opt/script/modifyReport.py");
command.add(TEMP_REPORT_PATH + randomId);
command.add(TEMP_REPORT_PATH + randomId + File.separator + originalName);
command.add(reportName);
return String.join(" ", command);
}
// 前端参数写入临时目录
FileOutputStream projectInfoOutputStream = null;
try {
projectInfoOutputStream = new FileOutputStream(TEMP_REPORT_PATH + randomId + File.separator + "reportContent.json");
projectInfoOutputStream.write(reportContent.getBytes(StandardCharsets.UTF_8));
projectInfoOutputStream.flush();
projectInfoOutputStream.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
// 调用脚本
log.info("执行 Python 命令: {}", commands);
int runningStatus = -1;
try {
log.info("开始同步执行脚本");
Process process = Runtime.getRuntime().exec(commands);
log.info("开始获取脚本输出");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
log.info("executePython" + line);
}
log.info("脚本执行完成");
runningStatus = process.waitFor();
log.info("脚本运行状态:" + runningStatus);
} catch (IOException | InterruptedException e) {
log.error("执行脚本失败:" + e);
return SdmResponse.failed("执行脚本失败");
/**
* 写入报告内容JSON到临时目录
*/
private void writeReportContentJson(String randomId, String reportContent) {
try (FileOutputStream outputStream = new FileOutputStream(TEMP_REPORT_PATH + randomId + File.separator + "reportContent.json")) {
outputStream.write(reportContent.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
} catch (Exception e) {
throw new RuntimeException("写入报告内容失败", e);
}
}
/**
* 执行Python脚本带5秒超时
*/
private SdmResponse executePythonScriptWithTimeout(String commands) {
log.info("执行 Python 命令: {}", commands);
Process process = null;
try {
log.info("开始同步执行脚本");
process = Runtime.getRuntime().exec(commands);
boolean finished = process.waitFor(5, TimeUnit.SECONDS);
if (!finished) {
log.error("脚本执行超时超过5秒未完成");
process.destroyForcibly();
return SdmResponse.failed("脚本执行超时,请稍后重试");
}
int runningStatus = process.exitValue();
log.info("脚本运行状态:" + runningStatus);
if (runningStatus != 0) {
log.error("执行脚本失败");
return SdmResponse.failed("执行脚本失败");
} else {
log.info(commands + "执行脚本完成!");
}
// 读取脚本生成的报告并归档
archiveReportAndImage(reportName, req.getTaskId(), req.getRunId(), randomId, FileBizTypeEnum.REPORT_FILE, null, null);
// 读取脚本生成的图片并按顺序归档
List<String> imageNames = extractPicNames(reportContent);
if (CollectionUtils.isNotEmpty(imageNames)) {
int sortOrder = 1;
for (String imageName : imageNames) {
archiveReportAndImage(null, req.getTaskId(), req.getRunId(), randomId, FileBizTypeEnum.CLOUD_FILE, imageName, sortOrder++);
}
}
log.info(commands + " 执行脚本完成!");
return SdmResponse.success();
// 删除临时路径
// log.info("删除临时路径:{},中。。。。。。", randomId);
// deleteFolder(new File(TEMP_REPORT_PATH + randomId));
} catch (IOException e) {
log.error("执行脚本失败:" + e);
return SdmResponse.failed("执行脚本失败");
} catch (InterruptedException e) {
log.error("脚本执行被中断:" + e);
Thread.currentThread().interrupt();
return SdmResponse.failed("脚本执行被中断");
} finally {
if (process != null) {
process.destroy();
}
}
return SdmResponse.failed("生成自动化报告失败");
}
/**
* 归档生成的报告和图片
*/
private void archiveGeneratedReport(String reportName, String taskId, String runId, String randomId, String reportContent) {
// 归档报告文件
archiveReportAndImage(reportName, taskId, runId, randomId, FileBizTypeEnum.REPORT_FILE, null, null);
// 归档图片
List<String> imageNames = extractPicNames(reportContent);
if (CollectionUtils.isNotEmpty(imageNames)) {
int sortOrder = 1;
for (String imageName : imageNames) {
archiveReportAndImage(null, taskId, runId, randomId, FileBizTypeEnum.CLOUD_FILE, imageName, sortOrder++);
}
}
}
/**
* 下载报告文件到响应流
*/
private void downloadReportFile(String reportName, String randomId, HttpServletResponse response) {
try (FileInputStream fileInputStream = new FileInputStream(TEMP_REPORT_PATH + randomId + File.separator + reportName)) {
byte[] fileData = fileInputStream.readAllBytes();
response.reset();
response.setContentType("application/octet-stream;charset=UTF-8");
response.addHeader("Content-Length", String.valueOf(fileData.length));
OutputStream outputStream = response.getOutputStream();
outputStream.write(fileData);
outputStream.flush();
} catch (Exception ex) {
log.error("下载报告文件失败:{}", ex.getMessage());
throw new RuntimeException("下载报告文件失败");
}
}
// ==================== 原有私有方法 ====================
private void archiveReportAndImage(String reportName, String taskId, String runId, String randomId, FileBizTypeEnum fileBizTypeEnum, String imageName, Integer sortOrder) {
byte[] fileData = null;
try {
@@ -2161,144 +2254,40 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
@Override
public void editReportAndDownload(EditReportReq req, HttpServletResponse response) {
Long userId = ThreadLocalContext.getUserId();
log.info("编辑报告参数为:{}", req);
Long userId = ThreadLocalContext.getUserId();
String reportContent = req.getReportContent();
if (StringUtils.isNotEmpty(req.getTaskId())) {
SimulationTask simulationTask = simulationTaskService.lambdaQuery().eq(SimulationTask::getUuid, req.getTaskId()).one();
if (simulationTask != null) {
// 任务绑定报告模板
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();
if (simulationRun != null) {
// 算例绑定报告模板
simulationRun.setReportTemplate(req.getReportTemplate());
simulationRun.setReportContent(reportContent);
this.updateById(simulationRun);
// 1. 更新报告绑定,获取任务名称
String taskName = updateReportBinding(req, userId);
// 读取前端传入的指标表格数据,实现更新/替换/新增
archivePerformances(reportContent, simulationRun.getTaskId(), simulationRun.getUuid(), userId);
}
}
String randomId = RandomUtil.generateString(16);
// 创建临时文件夹
Path folder = Paths.get(TEMP_REPORT_PATH + randomId);
if (!Files.exists(folder) || !Files.isDirectory(folder)) {
if (!new File(TEMP_REPORT_PATH + randomId).mkdir()) {
log.error("创建临时文件夹:{}失败",TEMP_REPORT_PATH + randomId);
throw new RuntimeException("生成报告失败,原因为:创建临时文件夹失败");
}
}
// 2. 创建临时文件夹
String randomId = createTempReportFolder();
log.info("临时路径为:{}", randomId);
// 根据文件id下载文件到临时目录
SdmResponse<ReportTemplateResp> reportResponse = reportFeignClient.queryReportTemplateInfo(req.getReportTemplate());
if (reportResponse.isSuccess()) {
Long reportTemplateFileId = reportResponse.getData().getFileId();
// 获取报告模板名称
GetFileBaseInfoReq getFileBaseInfoReq = new GetFileBaseInfoReq();
getFileBaseInfoReq.setFileId(reportTemplateFileId);
SdmResponse<FileMetadataInfoResp> fileBaseInfoResp = dataFeignClient.getFileBaseInfo(getFileBaseInfoReq);
String originalName = fileBaseInfoResp.getData().getOriginalName();
// 下载到本地临时目录
dataFeignClient.downloadFileToLocal(reportTemplateFileId, TEMP_REPORT_PATH + randomId);
String reportName = "report_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +
".docx";
// 构建python命令
List<String> command = new ArrayList<>();
command.add("python");
command.add("/opt/script/modifyReport.py");
// command.add(TEMP_REPORT_PATH + File.separator +"modifyReport.py");
command.add(TEMP_REPORT_PATH + randomId);
command.add(TEMP_REPORT_PATH + randomId + File.separator + originalName);
command.add(reportName);
String commands = String.join(" ", command);
// 前端参数写入临时目录
FileOutputStream projectInfoOutputStream = null;
try {
projectInfoOutputStream = new FileOutputStream(TEMP_REPORT_PATH + randomId + File.separator + "reportContent.json");
projectInfoOutputStream.write(reportContent.getBytes(StandardCharsets.UTF_8));
projectInfoOutputStream.flush();
projectInfoOutputStream.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
// 调用脚本
log.info("执行 Python 命令: {}", commands);
int runningStatus = -1;
try {
log.info("开始同步执行脚本");
Process process = Runtime.getRuntime().exec(commands);
log.info("开始获取脚本输出");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
log.info("executePython" + line);
}
log.info("脚本执行完成");
runningStatus = process.waitFor();
log.info("脚本运行状态:" + runningStatus);
} catch (IOException | InterruptedException e) {
log.error("执行脚本失败:" + e);
return;
}
if (runningStatus != 0) {
log.error("执行脚本失败");
return;
} else {
log.info(commands + "执行脚本完成!");
}
byte[] fileData = null;
if (response != null) {
try {
// 读取脚本生成的报告并归档
archiveReportAndImage(reportName, req.getTaskId(), req.getRunId(), randomId, FileBizTypeEnum.REPORT_FILE, null, null);
// 读取脚本生成的图片并按顺序归档
List<String> imageNames = extractPicNames(reportContent);
if (CollectionUtils.isNotEmpty(imageNames)) {
int sortOrder = 1;
for (String imageName : imageNames) {
archiveReportAndImage(null, req.getTaskId(), req.getRunId(), randomId, FileBizTypeEnum.CLOUD_FILE, imageName, sortOrder++);
}
}
FileInputStream fileInputStream = new FileInputStream(TEMP_REPORT_PATH + randomId + File.separator + reportName);
fileData = fileInputStream.readAllBytes();
// 下载到本地
// 设置响应头
response.reset();
response.setContentType("application/octet-stream;charset=UTF-8");
response.addHeader("Content-Length", String.valueOf(fileData.length));
// 写入响应流
OutputStream outputStream = response.getOutputStream();
outputStream.write(fileData);
outputStream.flush();
outputStream.close();
fileInputStream.close();
} catch (Exception ex) {
log.error("生成自动化报告失败:{}", ex.getMessage());
throw new RuntimeException("生成自动化报告失败");
}
}
// 删除临时路径
// log.info("删除临时路径:{},中。。。。。。", randomId);
// deleteFolder(new File(TEMP_REPORT_PATH + randomId));
// 3. 准备报告模板
String originalName = prepareReportTemplate(req.getReportTemplate(), randomId);
if (originalName == null) {
throw new RuntimeException("获取报告模板失败");
}
// 4. 生成报告名称(使用任务名称作为前缀)
String reportName = generateReportName(taskName);
// 5. 构建并执行Python脚本
String commands = buildModifyReportCommand(randomId, originalName, reportName);
writeReportContentJson(randomId, reportContent);
SdmResponse executeResult = executePythonScriptWithTimeout(commands);
if (!executeResult.isSuccess()) {
throw new RuntimeException(executeResult.getMessage());
}
// 6. 归档报告和图片
archiveGeneratedReport(reportName, req.getTaskId(), req.getRunId(), randomId, reportContent);
// 7. 下载报告文件
downloadReportFile(reportName, randomId, response);
}
@Override

View File

@@ -3166,6 +3166,7 @@ public class TaskServiceImpl implements ITaskService {
}
taskVo.setNodeName(taskVo.getTaskName());
taskVo.setNodeType("task");
taskVo.setNodeCode(taskVo.getTaskCode());
List<SpdmTaskMemberVo> memberList = mapper.getMemberList(Collections.singletonList(taskVo.getUuid()), null);
if (CollectionUtils.isNotEmpty(memberList)) {
List<Long> userIdList = memberList.stream().map(SpdmTaskMemberVo::getUserId).toList();