diff --git a/java/pom.xml b/java/pom.xml index db07c4b..4735b95 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -98,6 +98,12 @@ spring-boot-starter-quartz + + com.opencsv + opencsv + 5.7.1 + + diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java index 7107819..0644eeb 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java @@ -426,12 +426,12 @@ public class TsFilesController { } - /** - * 查询文件内容接口 - * - * @param id 文件的id - * @return 文件内容的纯文本(UTF-8 编码) - */ + + /********************************** + * 用途说明: 查询文件内容接口 + * 参数说明 id 文件的ID + * 返回值说明: com.yfd.platform.config.ResponseResult文件内容的纯文本(UTF-8 编码) + ***********************************/ @Log(module = "实验数据管理", value = "查询文件内容!") @GetMapping("/api/files/content") @ApiOperation("查询文件内容") @@ -447,13 +447,12 @@ public class TsFilesController { } } - /** - * 保存文件内容接口 - * - * @param id 文件的id - * @param content 新的文件内容(HTML/文本) - * @return 操作结果 - */ + /********************************** + * 用途说明: 保存文件内容接口 + * 参数说明 id 文件的ID + * 参数说明 content 新的文件内容(HTML/文本) + * 返回值说明: com.yfd.platform.config.ResponseResult操作结果 + ***********************************/ @Log(module = "实验数据管理", value = "保存文件内容!") @PostMapping("/save/files/content") @ApiOperation("保存文件内容") @@ -470,5 +469,19 @@ public class TsFilesController { } + /********************************** + * 用途说明: 批量修改文件中多行多列的内容 + * 参数说明 request 要修改的文件信息 + * 返回值说明: com.yfd.platform.config.ResponseResult操作结果 + ***********************************/ + @Log(module = "实验数据管理", value = "批量修改文件中多行多列的内容!") + @PostMapping("/batchModify") + @ApiOperation("批量修改文件中多行多列的内容") + public ResponseResult batchModifyFile(@RequestBody BatchModifyRequest request) throws IOException { + tsFilesService.batchUpdateFile(request.getId(), request.getModifications()); + return ResponseResult.success("文件保存成功"); + } + + } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/BatchModifyRequest.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/BatchModifyRequest.java new file mode 100644 index 0000000..cd74727 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/BatchModifyRequest.java @@ -0,0 +1,18 @@ +package com.yfd.platform.modules.experimentalData.domain; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import java.util.List; + +// 批量修改请求对象 +@Data +public class BatchModifyRequest { + + @NotBlank + private String id; // 文件ID + + @NotEmpty + private List modifications; +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/ModifyCommand.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/ModifyCommand.java new file mode 100644 index 0000000..9273fc8 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/ModifyCommand.java @@ -0,0 +1,20 @@ +package com.yfd.platform.modules.experimentalData.domain; + +import lombok.Data; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; + +// 单条修改指令 +@Data +public class ModifyCommand { + + @Min(1) + private int lineNum; // 行号(从1开始) + + @Min(1) + private int colNum; // 列号(从1开始) + + @NotBlank + private String newValue; +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java index ec51c28..5c60d19 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java @@ -2,10 +2,7 @@ package com.yfd.platform.modules.experimentalData.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.yfd.platform.config.ResponseResult; -import com.yfd.platform.modules.experimentalData.domain.DualTreeResponse; -import com.yfd.platform.modules.experimentalData.domain.MoveCopyFileFolderRequest; -import com.yfd.platform.modules.experimentalData.domain.Parameter; -import com.yfd.platform.modules.experimentalData.domain.TsFiles; +import com.yfd.platform.modules.experimentalData.domain.*; import com.baomidou.mybatisplus.extension.service.IService; import org.springframework.web.bind.annotation.RequestParam; @@ -170,12 +167,18 @@ public interface ITsFilesService extends IService { */ String readFileContent(String id) throws IOException; - /** - * 保存文件内容接口 - * - * @param id 文件的id - * @param content 新的文件内容(HTML/文本) - * @return 操作结果 - */ + /********************************** + * 用途说明: 保存文件内容接口 + * 参数说明 id 文件的ID + * 参数说明 content 新的文件内容(HTML/文本) + * 返回值说明: com.yfd.platform.config.ResponseResult操作结果 + ***********************************/ void saveFileContent(String id, String content) throws IOException; + + /********************************** + * 用途说明: 批量修改文件中多行多列的内容 + * 参数说明 request 要修改的文件信息 + * 返回值说明: com.yfd.platform.config.ResponseResult操作结果 + ***********************************/ + void batchUpdateFile(String id, List modifications) throws IOException; } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java index 9f1e8e7..8cf2ffc 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java @@ -3,6 +3,8 @@ package com.yfd.platform.modules.experimentalData.service.impl; import cn.hutool.core.collection.CollUtil; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; @@ -24,6 +26,9 @@ import com.amazonaws.services.s3.model.S3ObjectInputStream; import com.amazonaws.util.IOUtils; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.opencsv.CSVReader; +import com.opencsv.CSVWriter; +import com.opencsv.exceptions.CsvValidationException; import com.yfd.platform.component.ServerSendEventServer; import com.yfd.platform.config.ResponseResult; import com.yfd.platform.modules.experimentalData.domain.*; @@ -62,7 +67,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - +import org.springframework.beans.factory.annotation.Value; // 正确 import javax.annotation.Resource; import javax.xml.crypto.Data; import java.io.*; @@ -108,6 +113,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl private List compressSuffixes; private final Set addedEntries = new HashSet<>(); + + /********************************** * 用途说明: 分页查询试验数据管理-文档内容 * 参数说明 @@ -4263,6 +4270,144 @@ public class TsFilesServiceImpl extends ServiceImpl impl } } + /********************************** + * 用途说明: 批量修改文件中多行多列的内容 + * 参数说明 request 要修改的文件信息 + * 返回值说明: com.yfd.platform.config.ResponseResult操作结果 + ***********************************/ + @Override + public void batchUpdateFile(String id, List modifications)throws IOException { + StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); + TsFiles tsFile = tsFilesMapper.selectById(id); + if (tsFile == null) { + throw new IllegalArgumentException("文件ID不存在: " + id); + } + Path filePath = Paths.get(config.getValue(), tsFile.getWorkPath(), tsFile.getFileName()).normalize(); + // 安全校验 + if (!filePath.startsWith(config.getValue())) { + throw new SecurityException("路径越界: " + filePath); + } + + // 1.2 检查文件是否为文本类型 + validateTextFile(filePath); + + LOGGER.info("批量修改文件: {}, 修改指令数: {}", filePath, modifications.size()); + + + // 3. 创建临时文件(在指定目录下) + Path tempDir = Paths.get(config.getValue(), tsFile.getWorkPath()).normalize(); + if (!Files.exists(tempDir)) { + Files.createDirectories(tempDir); // 确保目录存在 + } + + // 临时文件名格式:原文件名_时间戳.tmp + String tempFileName = tsFile.getFileName() + "_" + System.currentTimeMillis() + ".tmp"; + Path tempPath = tempDir.resolve(tempFileName); + + try { + // 读取文件内容(兼容Java 8) + List lines = new ArrayList<>(); + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + } + + applyModifications(lines, modifications); + + // 写入临时文件(兼容Java 8) + try (BufferedWriter writer = Files.newBufferedWriter(tempPath, StandardCharsets.UTF_8)) { + for (String line : lines) { + writer.write(line); + writer.newLine(); // 保留原换行符风格 + } + } + + Files.move(tempPath, filePath, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + Files.deleteIfExists(tempPath); + throw e; + } + } + + + /** + * 应用所有修改到文件内容 + * @param lines 文件每一行的内容 + * @param modifications 修改指令集合 + */ + private void applyModifications(List lines, List modifications) { + modifications.forEach(cmd -> { + if (cmd.getLineNum() > lines.size()) { + throw new IllegalArgumentException("行号超出范围: " + cmd.getLineNum()); + } + String originalLine = lines.get(cmd.getLineNum() - 1); + String modifiedLine = modifyLine(originalLine, cmd.getColNum(), cmd.getNewValue()); + lines.set(cmd.getLineNum() - 1, modifiedLine); + }); + } + + + private void validateTextFile(Path filePath) throws IOException { + String mimeType = Files.probeContentType(filePath); + if (mimeType == null || !mimeType.startsWith("text/")) { + throw new IOException("只允许修改文本文件,检测到类型: " + mimeType); + } + } + + + private String modifyTabDelimitedLine(String line, int colNum, String newValue) { + String[] columns = line.split("\t", -1); // -1保留空列 + if (colNum < 1 || colNum > columns.length) { + throw new IllegalArgumentException("列号越界: " + colNum); + } + columns[colNum - 1] = newValue; + return String.join("\t", columns); + } + + private String modifyCsvLine(String line, int colNum, String newValue) { + try (CSVReader reader = new CSVReader(new StringReader(line))) { + String[] columns = reader.readNext(); + if (colNum < 1 || colNum > columns.length) { + throw new IllegalArgumentException("列号越界: " + colNum); + } + columns[colNum - 1] = newValue; + + StringWriter sw = new StringWriter(); + try (CSVWriter writer = new CSVWriter(sw)) { + writer.writeNext(columns); + return sw.toString().trim(); // 去除末尾换行 + } + } catch (IOException | CsvValidationException e) { + throw new UncheckedIOException("CSV解析失败", (IOException) e); + } + } + + + private String modifySpaceDelimitedLine(String line, int colNum, String newValue) { + // 处理连续多个空格(保留原始对齐) + String[] columns = line.split("\\s+", -1); + if (colNum < 1 || colNum > columns.length) { + throw new IllegalArgumentException("列号越界: " + colNum); + } + columns[colNum - 1] = newValue; + + // 重建原始空格对齐(假设原文件用固定空格数分隔) + return String.join(" ", columns); // 用4个空格分隔 + } + + private String modifyLine(String line, int colNum, String newValue) { + // 自动检测分隔符类型 + if (line.contains("\t")) { + return modifyTabDelimitedLine(line, colNum, newValue); + } else if (line.contains(",")) { + return modifyCsvLine(line, colNum, newValue); + } else { + return modifySpaceDelimitedLine(line, colNum, newValue); + } + } + }