提交代码0323
This commit is contained in:
parent
1f1208940f
commit
e7b9a1d7a9
@ -174,11 +174,11 @@ public class TsFilesController {
|
|||||||
@PostMapping("/deleteTsFilesByIds")
|
@PostMapping("/deleteTsFilesByIds")
|
||||||
@ApiOperation("批量删除试验数据管理文档内容")
|
@ApiOperation("批量删除试验数据管理文档内容")
|
||||||
@PreAuthorize("@el.check('del:tsFiles')")
|
@PreAuthorize("@el.check('del:tsFiles')")
|
||||||
public ResponseResult deleteTsFilesByIds(@RequestParam String id, @RequestParam String type) {
|
public ResponseResult deleteTsFilesByIds(@RequestParam String ids, @RequestParam String type) {
|
||||||
if (StrUtil.isBlank(id)) {
|
if (StrUtil.isBlank(ids)) {
|
||||||
return ResponseResult.error("参数为空");
|
return ResponseResult.error("参数为空");
|
||||||
}
|
}
|
||||||
String[] splitIds = id.split(",");
|
String[] splitIds = ids.split(",");
|
||||||
// 数组转集合
|
// 数组转集合
|
||||||
List<String> dataset = Arrays.asList(splitIds);
|
List<String> dataset = Arrays.asList(splitIds);
|
||||||
return ResponseResult.success(tsFilesService.deleteTsFilesByIds(dataset, type));
|
return ResponseResult.success(tsFilesService.deleteTsFilesByIds(dataset, type));
|
||||||
@ -417,4 +417,48 @@ public class TsFilesController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文件内容接口
|
||||||
|
*
|
||||||
|
* @param id 文件的id
|
||||||
|
* @return 文件内容的纯文本(UTF-8 编码)
|
||||||
|
*/
|
||||||
|
@Log(module = "实验数据管理", value = "查询文件内容!")
|
||||||
|
@GetMapping("/api/files/content")
|
||||||
|
@ApiOperation("查询文件内容")
|
||||||
|
public ResponseResult getFileContent(@RequestParam String id) {
|
||||||
|
try {
|
||||||
|
if (StrUtil.isBlank(id)) {
|
||||||
|
return ResponseResult.error("参数为空");
|
||||||
|
}
|
||||||
|
String content = tsFilesService.readFileContent(id);
|
||||||
|
return ResponseResult.successData(content);
|
||||||
|
} catch (SecurityException | IOException e) {
|
||||||
|
return ResponseResult.error("非法操作: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存文件内容接口
|
||||||
|
*
|
||||||
|
* @param id 文件的id
|
||||||
|
* @param content 新的文件内容(HTML/文本)
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
@Log(module = "实验数据管理", value = "保存文件内容!")
|
||||||
|
@PostMapping("/save/files/content")
|
||||||
|
@ApiOperation("保存文件内容")
|
||||||
|
public ResponseResult saveFileContent( String id, String content) {
|
||||||
|
try {
|
||||||
|
if (StrUtil.isBlank(id) && StrUtil.isBlank(content)) {
|
||||||
|
return ResponseResult.error("参数为空");
|
||||||
|
}
|
||||||
|
tsFilesService.saveFileContent(id, content);
|
||||||
|
return ResponseResult.success("文件保存成功");
|
||||||
|
} catch (SecurityException | IOException e) {
|
||||||
|
return ResponseResult.error("非法操作: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -161,4 +161,21 @@ public interface ITsFilesService extends IService<TsFiles> {
|
|||||||
TsFiles compareDirectories(List<String> dataset,String nodeId , String taskId);
|
TsFiles compareDirectories(List<String> dataset,String nodeId , String taskId);
|
||||||
|
|
||||||
void batchSendNaviOutDataJob(String id, int samTimes,String token);
|
void batchSendNaviOutDataJob(String id, int samTimes,String token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文件内容接口
|
||||||
|
*
|
||||||
|
* @param id 文件的id
|
||||||
|
* @return 文件内容的纯文本(UTF-8 编码)
|
||||||
|
*/
|
||||||
|
String readFileContent(String id) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存文件内容接口
|
||||||
|
*
|
||||||
|
* @param id 文件的id
|
||||||
|
* @param content 新的文件内容(HTML/文本)
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
void saveFileContent(String id, String content) throws IOException;
|
||||||
}
|
}
|
||||||
|
@ -49,11 +49,13 @@ import io.netty.handler.codec.compression.CompressionException;
|
|||||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.ArchiveOutputStream;
|
import org.apache.commons.compress.archivers.ArchiveOutputStream;
|
||||||
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
|
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
|
||||||
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
|
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||||
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
|
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
|
||||||
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
|
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
|
||||||
@ -307,9 +309,9 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
if (StringUtils.isNotBlank(id)) {
|
if (StringUtils.isNotBlank(id)) {
|
||||||
queryWrapper.eq("parent_id", id);
|
queryWrapper.eq("parent_id", id);
|
||||||
}
|
}
|
||||||
// if (StringUtils.isNotBlank(path)) {
|
if (StringUtils.isNotBlank(path)) {
|
||||||
// queryWrapper.eq("work_path", path);
|
queryWrapper.eq("work_path", path);
|
||||||
// }
|
}
|
||||||
if (StringUtils.isNotBlank(nodeId)) {
|
if (StringUtils.isNotBlank(nodeId)) {
|
||||||
queryWrapper.eq("node_id", nodeId);
|
queryWrapper.eq("node_id", nodeId);
|
||||||
}
|
}
|
||||||
@ -372,7 +374,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
// //判断文件夹是否创建
|
// //判断文件夹是否创建
|
||||||
// AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey("local");
|
// AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey("local");
|
||||||
// boolean flag = fileService.isFolderCreated(File.separator + tsFiles.getNodeId());
|
// boolean flag = fileService.isFolderCreated(File.separator + tsFiles.getNodeId());
|
||||||
// //如果是false 说明没有创建 那就新建一个文件夹
|
//如果是false 说明没有创建 那就新建一个文件夹
|
||||||
// if(!flag){
|
// if(!flag){
|
||||||
// //本地创建文件夹
|
// //本地创建文件夹
|
||||||
// NewFolderRequest newFolderRequest = new NewFolderRequest();
|
// NewFolderRequest newFolderRequest = new NewFolderRequest();
|
||||||
@ -1512,244 +1514,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查压缩文件是否包含文件夹(主入口方法)
|
|
||||||
*
|
|
||||||
* @param filePath 压缩文件路径
|
|
||||||
* @return 是否包含文件夹
|
|
||||||
*/
|
|
||||||
public boolean hasFolder(Path filePath) throws IOException {
|
|
||||||
String fileName = filePath.getFileName().toString().toLowerCase();
|
|
||||||
QueryWrapper<SysDictionaryItems> queryWrapperSysDictionary = new QueryWrapper<>();
|
|
||||||
queryWrapperSysDictionary.eq("parentcode", "compressType");
|
|
||||||
queryWrapperSysDictionary.orderByAsc("orderno");
|
|
||||||
List<SysDictionaryItems> sysDictionaryItems = sysDictionaryItemsMapper.selectList(queryWrapperSysDictionary);
|
|
||||||
// 遍历所有支持的压缩后缀进行匹配
|
|
||||||
for (SysDictionaryItems sys : sysDictionaryItems) {
|
|
||||||
if (fileName.endsWith("." + sys.getDictName())) {
|
|
||||||
return checkBySuffix(filePath, sys.getDictName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IOException("不支持的压缩格式: " + fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化压缩后缀配置
|
|
||||||
*
|
|
||||||
* @param sysDictionaryItems 数据库查询结果集
|
|
||||||
*/
|
|
||||||
public void initCompressTypes(List<SysDictionaryItems> sysDictionaryItems) {
|
|
||||||
// 按后缀长度降序排序(确保优先匹配长后缀如.tar.gz)
|
|
||||||
this.compressSuffixes = sysDictionaryItems.stream()
|
|
||||||
.map(item -> item.getDictName().toLowerCase())
|
|
||||||
.sorted((s1, s2) -> Integer.compare(s2.length(), s1.length()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据后缀分派检查方法
|
|
||||||
*
|
|
||||||
* @param path 文件路径
|
|
||||||
* @param suffix 压缩后缀(不带点)
|
|
||||||
*/
|
|
||||||
private boolean checkBySuffix(Path path, String suffix) throws IOException {
|
|
||||||
switch (suffix) {
|
|
||||||
case "zip":
|
|
||||||
return checkZip(path);
|
|
||||||
case "tar":
|
|
||||||
return checkTar(path);
|
|
||||||
case "gz":
|
|
||||||
return checkGzip(path);
|
|
||||||
case "bz2":
|
|
||||||
return checkBzip2(path);
|
|
||||||
case "rar":
|
|
||||||
return checkRar(path);
|
|
||||||
case "tar.gz":
|
|
||||||
return checkTarGz(path);
|
|
||||||
case "tar.bz2":
|
|
||||||
return checkTarBz2(path);
|
|
||||||
default:
|
|
||||||
throw new IOException("未实现的格式检查: " + suffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/******************** ZIP格式检查 ********************/
|
|
||||||
/**
|
|
||||||
* 检查ZIP文件是否包含目录
|
|
||||||
*
|
|
||||||
* @param zipFilePath ZIP文件路径
|
|
||||||
* @return 是否包含目录
|
|
||||||
*/
|
|
||||||
private boolean checkZip(Path zipFilePath) throws IOException {
|
|
||||||
try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) {
|
|
||||||
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
|
||||||
while (entries.hasMoreElements()) {
|
|
||||||
ZipEntry entry = entries.nextElement();
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************** TAR格式检查 ********************/
|
|
||||||
/**
|
|
||||||
* 检查TAR文件是否包含目录
|
|
||||||
*
|
|
||||||
* @param tarFilePath TAR文件路径
|
|
||||||
* @return 是否包含目录
|
|
||||||
*/
|
|
||||||
private boolean checkTar(Path tarFilePath) throws IOException {
|
|
||||||
try (TarArchiveInputStream tis = new TarArchiveInputStream(Files.newInputStream(tarFilePath))) {
|
|
||||||
TarArchiveEntry entry;
|
|
||||||
while ((entry = tis.getNextTarEntry()) != null) {
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************** GZIP格式检查 ********************/
|
|
||||||
/**
|
|
||||||
* 检查GZ文件是否包含目录(可能包含嵌套TAR)
|
|
||||||
*
|
|
||||||
* @param gzFilePath GZ文件路径
|
|
||||||
* @return 是否包含目录
|
|
||||||
*/
|
|
||||||
private boolean checkGzip(Path gzFilePath) throws IOException {
|
|
||||||
// 检查是否为TAR.GZ格式
|
|
||||||
try (InputStream is = Files.newInputStream(gzFilePath);
|
|
||||||
GzipCompressorInputStream gzis = new GzipCompressorInputStream(is)) {
|
|
||||||
|
|
||||||
// 读取头部判断是否嵌套TAR
|
|
||||||
byte[] header = new byte[512];
|
|
||||||
if (gzis.read(header) >= 512 && isTarHeader(header)) {
|
|
||||||
return checkTar(new ByteArrayInputStream(header, 0, 512), gzis);
|
|
||||||
}
|
|
||||||
return false; // 纯GZ文件不含目录
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************** TAR.GZ复合格式检查 ********************/
|
|
||||||
/**
|
|
||||||
* 检查TAR.GZ文件是否包含目录
|
|
||||||
*
|
|
||||||
* @param tgzFilePath TAR.GZ文件路径
|
|
||||||
* @return 是否包含目录
|
|
||||||
*/
|
|
||||||
private boolean checkTarGz(Path tgzFilePath) throws IOException {
|
|
||||||
try (InputStream is = Files.newInputStream(tgzFilePath);
|
|
||||||
GzipCompressorInputStream gzis = new GzipCompressorInputStream(is);
|
|
||||||
TarArchiveInputStream tis = new TarArchiveInputStream(gzis)) {
|
|
||||||
|
|
||||||
return checkTarEntries(tis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************** BZIP2格式检查 ********************/
|
|
||||||
/**
|
|
||||||
* 检查BZ2文件是否包含目录(可能包含嵌套TAR)
|
|
||||||
*
|
|
||||||
* @param bz2FilePath BZ2文件路径
|
|
||||||
* @return 是否包含目录
|
|
||||||
*/
|
|
||||||
private boolean checkBzip2(Path bz2FilePath) throws IOException {
|
|
||||||
try (InputStream is = Files.newInputStream(bz2FilePath);
|
|
||||||
BZip2CompressorInputStream bz2is = new BZip2CompressorInputStream(is)) {
|
|
||||||
|
|
||||||
// 读取头部判断是否嵌套TAR
|
|
||||||
byte[] header = new byte[512];
|
|
||||||
if (bz2is.read(header) >= 512 && isTarHeader(header)) {
|
|
||||||
return checkTar(new ByteArrayInputStream(header, 0, 512), bz2is);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************** TAR.BZ2复合格式检查 ********************/
|
|
||||||
/**
|
|
||||||
* 检查TAR.BZ2文件是否包含目录
|
|
||||||
*
|
|
||||||
* @param tbz2FilePath TAR.BZ2文件路径
|
|
||||||
* @return 是否包含目录
|
|
||||||
*/
|
|
||||||
private boolean checkTarBz2(Path tbz2FilePath) throws IOException {
|
|
||||||
try (InputStream is = Files.newInputStream(tbz2FilePath);
|
|
||||||
BZip2CompressorInputStream bz2is = new BZip2CompressorInputStream(is);
|
|
||||||
TarArchiveInputStream tis = new TarArchiveInputStream(bz2is)) {
|
|
||||||
|
|
||||||
return checkTarEntries(tis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************** RAR格式检查 ********************/
|
|
||||||
/**
|
|
||||||
* 检查RAR文件是否包含目录
|
|
||||||
*
|
|
||||||
* @param rarFilePath RAR文件路径
|
|
||||||
* @return 是否包含目录
|
|
||||||
*/
|
|
||||||
private boolean checkRar(Path rarFilePath) throws IOException {
|
|
||||||
try (Archive archive = new Archive(rarFilePath.toFile())) {
|
|
||||||
FileHeader fileHeader;
|
|
||||||
while ((fileHeader = archive.nextFileHeader()) != null) {
|
|
||||||
if (fileHeader.isDirectory()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IOException("解析RAR文件失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************** 通用工具方法 ********************/
|
|
||||||
/**
|
|
||||||
* 检查TAR流是否包含目录
|
|
||||||
*
|
|
||||||
* @param tis TAR输入流
|
|
||||||
* @return 是否包含目录
|
|
||||||
*/
|
|
||||||
private boolean checkTarEntries(TarArchiveInputStream tis) throws IOException {
|
|
||||||
TarArchiveEntry entry;
|
|
||||||
while ((entry = tis.getNextTarEntry()) != null) {
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断字节流是否为TAR头部
|
|
||||||
*
|
|
||||||
* @param header 512字节头部数据
|
|
||||||
* @return 是否TAR格式
|
|
||||||
*/
|
|
||||||
private boolean isTarHeader(byte[] header) {
|
|
||||||
if (header.length < 263) return false;
|
|
||||||
String magic = new String(header, 257, 6, StandardCharsets.US_ASCII);
|
|
||||||
return magic.startsWith("ustar");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并头部和剩余流检查TAR
|
|
||||||
*
|
|
||||||
* @param headerStream 头部字节流
|
|
||||||
* @param remainingStream 剩余数据流
|
|
||||||
*/
|
|
||||||
private boolean checkTar(InputStream headerStream, InputStream remainingStream) throws IOException {
|
|
||||||
try (TarArchiveInputStream tis = new TarArchiveInputStream(
|
|
||||||
new SequenceInputStream(headerStream, remainingStream))) {
|
|
||||||
return checkTarEntries(tis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建文件记录
|
* 创建文件记录
|
||||||
*/
|
*/
|
||||||
@ -1891,217 +1655,316 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 从字典表获取支持的压缩文件后缀列表
|
|
||||||
private List<String> getSupportedCompressionSuffixes() {
|
|
||||||
QueryWrapper<SysDictionaryItems> queryWrapperSysDictionary = new QueryWrapper<>();
|
|
||||||
queryWrapperSysDictionary.eq("parentcode", "compressType");
|
|
||||||
queryWrapperSysDictionary.orderByAsc("orderno");
|
|
||||||
List<SysDictionaryItems> sysDictionaryItems = sysDictionaryItemsMapper.selectList(queryWrapperSysDictionary);
|
|
||||||
|
|
||||||
// 提取后缀列表(例如 ["gz", "xz", "rar", "tar", "zip", "bz2", "tar.gz"])
|
|
||||||
return sysDictionaryItems.stream()
|
|
||||||
.map(SysDictionaryItems::getDictName)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解压 ZIP 文件到临时目录
|
|
||||||
*
|
|
||||||
* @param zipFilePath ZIP 文件路径
|
|
||||||
* @param baseDir 基础解压目录(如 /var/storage)
|
|
||||||
* @return 解压后的根目录 File 对象
|
|
||||||
*/
|
|
||||||
//这个方法会直接覆盖 下面那个不会 那个会报错 TODO
|
|
||||||
// 1. 检测压缩包是否包含文件夹结构(目录或路径分隔符)
|
|
||||||
|
// 新增:统一解压入口方法(保持原有逻辑结构)
|
||||||
private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException {
|
private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException {
|
||||||
Path destRoot = Paths.get(baseDir);
|
String fileName = zipFilePath.getFileName().toString();
|
||||||
Files.createDirectories(destRoot);
|
String ext = getFileExtension(fileName).toLowerCase();
|
||||||
|
|
||||||
// 获取支持的压缩后缀列表
|
// 根据文件类型调用不同解压实现
|
||||||
List<String> supportedSuffixes = getSupportedCompressionSuffixes();
|
if (ext.equals(".zip")) {
|
||||||
|
return unzipFile(zipFilePath, baseDir);
|
||||||
// 根据文件扩展名匹配支持的压缩格式(优先匹配长后缀,如 .tar.gz)
|
} else if (fileName.endsWith(".tar.gz")) {
|
||||||
String fileName = zipFilePath.getFileName().toString().toLowerCase();
|
return unTarGzFile(zipFilePath, baseDir);
|
||||||
String matchedSuffix = supportedSuffixes.stream()
|
} else if (ext.equals(".7z")) {
|
||||||
.filter(suffix -> fileName.endsWith("." + suffix))
|
return un7zFile(zipFilePath, baseDir);
|
||||||
.max(Comparator.comparingInt(String::length)) // 优先匹配最长后缀(如 .tar.gz)
|
} else {
|
||||||
.orElseThrow(() -> new IOException("Unsupported compression format: " + fileName));
|
throw new IllegalArgumentException("不支持的压缩格式: " + ext);
|
||||||
|
|
||||||
// 根据后缀选择解压方式
|
|
||||||
switch (matchedSuffix) {
|
|
||||||
case "zip":
|
|
||||||
return unzipToTemp(zipFilePath, destRoot);
|
|
||||||
case "gz":
|
|
||||||
return decompressGzip(zipFilePath, destRoot);
|
|
||||||
// case "bz2":
|
|
||||||
// return decompressBzip2(zipFilePath, destRoot);
|
|
||||||
// case "xz":
|
|
||||||
// return decompressXz(zipFilePath, destRoot);
|
|
||||||
case "tar":
|
|
||||||
return decompressTar(zipFilePath, destRoot);
|
|
||||||
case "tar.gz":
|
|
||||||
return decompressTarGz(zipFilePath, destRoot);
|
|
||||||
// case "tar.bz2":
|
|
||||||
// return decompressTarBz2(zipFilePath, destRoot);
|
|
||||||
case "rar":
|
|
||||||
return decompressRar(zipFilePath, destRoot);
|
|
||||||
default:
|
|
||||||
throw new IOException("Unsupported compression format: " + matchedSuffix);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 修改后的判断是否包含文件夹方法(支持多格式)
|
||||||
* 解压 ZIP 文件
|
private boolean hasFolder(Path zipFilePath) throws IOException {
|
||||||
*/
|
String fileName = zipFilePath.getFileName().toString();
|
||||||
private File unzipToTemp(Path zipFilePath, Path destRoot) throws IOException {
|
String ext = getFileExtension(fileName).toLowerCase();
|
||||||
String zipName = getFileNameWithoutExtension(zipFilePath.getFileName().toString());
|
|
||||||
|
if (ext.equals(".zip")) {
|
||||||
|
return hasFolderInZip(zipFilePath);
|
||||||
|
} else if (fileName.endsWith(".tar.gz")) {
|
||||||
|
return hasFolderInTarGz(zipFilePath);
|
||||||
|
} else if (ext.equals(".7z")) {
|
||||||
|
return hasFolderIn7z(zipFilePath);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("不支持的压缩格式: " + ext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 保持原有ZIP判断方法不变
|
||||||
|
private boolean hasFolderInZip(Path zipFilePath) throws IOException {
|
||||||
|
try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) {
|
||||||
|
return zipFile.stream()
|
||||||
|
.anyMatch(entry -> entry.isDirectory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:TAR.GZ格式文件夹判断
|
||||||
|
private boolean hasFolderInTarGz(Path filePath) throws IOException {
|
||||||
|
try (InputStream fi = Files.newInputStream(filePath);
|
||||||
|
InputStream gi = new GzipCompressorInputStream(fi);
|
||||||
|
TarArchiveInputStream ti = new TarArchiveInputStream(gi)) {
|
||||||
|
|
||||||
|
TarArchiveEntry entry;
|
||||||
|
while ((entry = ti.getNextTarEntry()) != null) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:7Z格式文件夹判断
|
||||||
|
private boolean hasFolderIn7z(Path filePath) throws IOException {
|
||||||
|
try (SevenZFile szFile = new SevenZFile(filePath.toFile())) {
|
||||||
|
SevenZArchiveEntry entry;
|
||||||
|
while ((entry = szFile.getNextEntry()) != null) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保持原有解压逻辑结构的ZIP实现
|
||||||
|
// 修改后的ZIP解压方法(修复解压问题)
|
||||||
|
private File unzipFile(Path sourcePath, String baseDir) throws IOException {
|
||||||
|
Path destRoot = Paths.get(baseDir);
|
||||||
|
Files.createDirectories(destRoot);
|
||||||
|
|
||||||
|
// 关键修复1:使用与压缩时相同的字符集
|
||||||
|
try (ZipInputStream zis = new ZipInputStream(
|
||||||
|
Files.newInputStream(sourcePath),
|
||||||
|
StandardCharsets.UTF_8)) {
|
||||||
|
|
||||||
|
// 关键修复2:记录已处理路径防止重复
|
||||||
|
Set<String> processedEntries = new HashSet<>();
|
||||||
|
|
||||||
try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zipFilePath))) {
|
|
||||||
ZipEntry entry;
|
ZipEntry entry;
|
||||||
while ((entry = zis.getNextEntry()) != null) {
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
// 过滤 MacOS 元数据目录
|
|
||||||
if (entry.getName().startsWith("__MACOSX/")) {
|
|
||||||
zis.closeEntry();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 剥离 ZIP 根目录(例如 ZIP 内有一个根目录 "example/")
|
|
||||||
String entryName = entry.getName();
|
String entryName = entry.getName();
|
||||||
if (entryName.startsWith(zipName + "/")) {
|
|
||||||
entryName = entryName.substring(zipName.length() + 1);
|
// 过滤系统文件(与压缩时逻辑对应)
|
||||||
}
|
if (entryName.startsWith("__MACOSX/")) {
|
||||||
// 跳过空路径
|
|
||||||
if (entryName.isEmpty()) {
|
|
||||||
zis.closeEntry();
|
zis.closeEntry();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 构建安全路径并写入文件
|
|
||||||
|
// 关键修复3:路径标准化处理
|
||||||
|
entryName = entryName.replace("\\", "/"); // 统一分隔符
|
||||||
|
if (processedEntries.contains(entryName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
processedEntries.add(entryName);
|
||||||
|
|
||||||
|
// 构建目标路径
|
||||||
Path destPath = destRoot.resolve(entryName).normalize();
|
Path destPath = destRoot.resolve(entryName).normalize();
|
||||||
validatePathSafety(destPath, destRoot);
|
validatePathSafetya(destPath, destRoot);
|
||||||
|
|
||||||
|
// 日志跟踪
|
||||||
|
LOGGER.debug("[解压] 处理条目:{} → {}", entryName, destPath);
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
Files.createDirectories(destPath);
|
Files.createDirectories(destPath);
|
||||||
|
LOGGER.debug("创建目录:{}", destPath);
|
||||||
} else {
|
} else {
|
||||||
Files.createDirectories(destPath.getParent());
|
// 关键修复4:确保父目录存在
|
||||||
writeFile(zis, destPath);
|
Path parentDir = destPath.getParent();
|
||||||
|
if (!Files.exists(parentDir)) {
|
||||||
|
Files.createDirectories(parentDir);
|
||||||
|
LOGGER.debug("创建父目录:{}", parentDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键修复5:使用与压缩时相同的写入方式
|
||||||
|
try (BufferedOutputStream bos = new BufferedOutputStream(
|
||||||
|
Files.newOutputStream(destPath,
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.TRUNCATE_EXISTING))) {
|
||||||
|
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int len;
|
||||||
|
while ((len = zis.read(buffer)) > 0) {
|
||||||
|
bos.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
// 立即刷新到磁盘
|
||||||
|
bos.flush();
|
||||||
|
LOGGER.info("已解压文件:{} (大小:{} bytes)",
|
||||||
|
destPath, Files.size(destPath));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
zis.closeEntry();
|
zis.closeEntry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 最终验证
|
||||||
|
LOGGER.info("解压完成,验证文件结构:");
|
||||||
|
Files.walk(destRoot)
|
||||||
|
.forEach(path -> LOGGER.info("-> {}", path));
|
||||||
|
|
||||||
return destRoot.toFile();
|
return destRoot.toFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解压 GZIP 文件(假设是单文件压缩)
|
* 路径安全验证(增强版)
|
||||||
*/
|
*/
|
||||||
private File decompressGzip(Path gzipFilePath, Path destRoot) throws IOException {
|
private void validatePathSafetya(Path targetPath, Path destRoot) throws IOException {
|
||||||
String fileName = getFileNameWithoutExtension(gzipFilePath.getFileName().toString());
|
Path normalizedDest = destRoot.toAbsolutePath().normalize();
|
||||||
Path outputFile = destRoot.resolve(fileName);
|
Path normalizedTarget = targetPath.toAbsolutePath().normalize();
|
||||||
|
|
||||||
try (InputStream is = Files.newInputStream(gzipFilePath);
|
if (!normalizedTarget.startsWith(normalizedDest)) {
|
||||||
GZIPInputStream gzis = new GZIPInputStream(is);
|
throw new IOException("路径安全验证失败:" + normalizedTarget
|
||||||
OutputStream os = Files.newOutputStream(outputFile)) {
|
+ " 超出根目录 " + normalizedDest);
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int len;
|
|
||||||
while ((len = gzis.read(buffer)) > 0) {
|
|
||||||
os.write(buffer, 0, len);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return outputFile.toFile();
|
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
/**
|
* 安全写入文件(带缓冲)
|
||||||
* 解压 TAR 文件
|
*/
|
||||||
*/
|
private void writeFile(ZipInputStream zis, Path destPath) throws IOException {
|
||||||
private File decompressTar(Path tarFilePath, Path destRoot) throws IOException {
|
try (BufferedOutputStream bos = new BufferedOutputStream(
|
||||||
try (TarArchiveInputStream tais = new TarArchiveInputStream(Files.newInputStream(tarFilePath))) {
|
Files.newOutputStream(destPath,
|
||||||
TarArchiveEntry entry;
|
StandardOpenOption.CREATE,
|
||||||
while ((entry = tais.getNextTarEntry()) != null) {
|
StandardOpenOption.TRUNCATE_EXISTING
|
||||||
// 过滤无效文件
|
)
|
||||||
if (entry.getName().startsWith("__MACOSX/")) continue;
|
)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
Path destPath = destRoot.resolve(entry.getName()).normalize();
|
int len;
|
||||||
validatePathSafety(destPath, destRoot);
|
while ((len = zis.read(buffer)) > 0) {
|
||||||
|
bos.write(buffer, 0, len);
|
||||||
if (entry.isDirectory()) {
|
|
||||||
Files.createDirectories(destPath);
|
|
||||||
} else {
|
|
||||||
Files.createDirectories(destPath.getParent());
|
|
||||||
writeFile(tais, destPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return destRoot.toFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解压 TAR.GZ 文件
|
* 内部类:存储 ZIP 条目信息
|
||||||
*/
|
*/
|
||||||
private File decompressTarGz(Path tarGzFilePath, Path destRoot) throws IOException {
|
private static class ZipEntryInfo {
|
||||||
try (InputStream is = Files.newInputStream(tarGzFilePath);
|
private final String name;
|
||||||
GZIPInputStream gzis = new GZIPInputStream(is);
|
private final boolean isDirectory;
|
||||||
TarArchiveInputStream tais = new TarArchiveInputStream(gzis)) {
|
|
||||||
|
|
||||||
TarArchiveEntry entry;
|
public ZipEntryInfo(String name, boolean isDirectory) {
|
||||||
while ((entry = tais.getNextTarEntry()) != null) {
|
this.name = name;
|
||||||
// 过滤无效文件
|
this.isDirectory = isDirectory;
|
||||||
if (entry.getName().startsWith("__MACOSX/")) continue;
|
}
|
||||||
|
|
||||||
Path destPath = destRoot.resolve(entry.getName()).normalize();
|
public String getName() {
|
||||||
validatePathSafety(destPath, destRoot);
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
public boolean isDirectory() {
|
||||||
Files.createDirectories(destPath);
|
return isDirectory;
|
||||||
} else {
|
|
||||||
Files.createDirectories(destPath.getParent());
|
|
||||||
writeFile(tais, destPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return destRoot.toFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解压 RAR 文件(需要 junrar 依赖)
|
|
||||||
*/
|
|
||||||
private File decompressRar(Path rarFilePath, Path destRoot) throws IOException {
|
|
||||||
try (Archive archive = new Archive(rarFilePath.toFile())) {
|
|
||||||
FileHeader fileHeader;
|
|
||||||
while ((fileHeader = archive.nextFileHeader()) != null) {
|
|
||||||
// 过滤无效文件
|
|
||||||
if (fileHeader.getFileName().startsWith("__MACOSX/")) continue;
|
|
||||||
|
|
||||||
Path destPath = destRoot.resolve(fileHeader.getFileName()).normalize();
|
|
||||||
validatePathSafety(destPath, destRoot);
|
|
||||||
|
|
||||||
if (fileHeader.isDirectory()) {
|
// 新增:TAR.GZ解压实现(保持相同路径逻辑)
|
||||||
Files.createDirectories(destPath);
|
private File unTarGzFile(Path sourcePath, String baseDir) throws IOException {
|
||||||
|
Path destRoot = Paths.get(baseDir);
|
||||||
|
Files.createDirectories(destRoot);
|
||||||
|
|
||||||
|
try (InputStream fi = Files.newInputStream(sourcePath);
|
||||||
|
InputStream gi = new GzipCompressorInputStream(fi);
|
||||||
|
TarArchiveInputStream ti = new TarArchiveInputStream(gi)) {
|
||||||
|
|
||||||
|
TarArchiveEntry entry;
|
||||||
|
while ((entry = ti.getNextTarEntry()) != null) {
|
||||||
|
// 保持相同过滤逻辑
|
||||||
|
if (entry.getName().startsWith("__MACOSX/")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path targetPath = destRoot.resolve(entry.getName()).normalize();
|
||||||
|
validatePathSafety(targetPath, destRoot);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
Files.createDirectories(targetPath);
|
||||||
} else {
|
} else {
|
||||||
Files.createDirectories(destPath.getParent());
|
Files.createDirectories(targetPath.getParent());
|
||||||
try (OutputStream os = Files.newOutputStream(destPath)) {
|
try (OutputStream os = Files.newOutputStream(targetPath)) {
|
||||||
archive.extractFile(fileHeader, os);
|
IOUtils.copy(ti, os);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (RarException e) {
|
|
||||||
throw new IOException("Failed to decompress RAR file", e);
|
|
||||||
}
|
}
|
||||||
return destRoot.toFile();
|
return destRoot.toFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 新增:7Z解压实现(保持相同路径逻辑)
|
||||||
* 将输入流写入目标文件
|
private File un7zFile(Path sourcePath, String baseDir) throws IOException {
|
||||||
*/
|
Path destRoot = Paths.get(baseDir);
|
||||||
private void writeFile(InputStream is, Path destPath) throws IOException {
|
Files.createDirectories(destRoot);
|
||||||
try (OutputStream os = Files.newOutputStream(destPath)) {
|
|
||||||
byte[] buffer = new byte[1024];
|
try (SevenZFile szFile = new SevenZFile(sourcePath.toFile())) {
|
||||||
int len;
|
SevenZArchiveEntry entry;
|
||||||
while ((len = is.read(buffer)) > 0) {
|
while ((entry = szFile.getNextEntry()) != null) {
|
||||||
os.write(buffer, 0, len);
|
// 保持相同过滤逻辑
|
||||||
|
if (entry.getName().startsWith("__MACOSX/")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path targetPath = destRoot.resolve(entry.getName()).normalize();
|
||||||
|
validatePathSafety(targetPath, destRoot);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
Files.createDirectories(targetPath);
|
||||||
|
} else {
|
||||||
|
Files.createDirectories(targetPath.getParent());
|
||||||
|
try (OutputStream os = Files.newOutputStream(targetPath)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int len;
|
||||||
|
while ((len = szFile.read(buffer)) > 0) {
|
||||||
|
os.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return destRoot.toFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * 解析压缩包条目(支持多种格式)
|
// * 解析压缩包条目(支持多种格式)
|
||||||
// */
|
// */
|
||||||
@ -2151,73 +2014,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
// return entries;
|
// return entries;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析压缩包条目,过滤无效文件(如 __MACOSX)
|
|
||||||
*/
|
|
||||||
private List<ZipEntryInfo> parseZipEntries(Path zipFilePath) throws IOException {
|
|
||||||
List<ZipEntryInfo> entries = new ArrayList<>();
|
|
||||||
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePath.toFile()))) {
|
|
||||||
ZipEntry entry;
|
|
||||||
while ((entry = zis.getNextEntry()) != null) {
|
|
||||||
if (!entry.getName().startsWith("__MACOSX/")) {
|
|
||||||
entries.add(new ZipEntryInfo(entry.getName(), entry.isDirectory()));
|
|
||||||
}
|
|
||||||
zis.closeEntry();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否为单文件结构
|
|
||||||
*/
|
|
||||||
private boolean isSingleFileStructure(List<ZipEntryInfo> entries) {
|
|
||||||
// 有效条目数量为1,且是文件(非目录),且不包含路径分隔符
|
|
||||||
if (entries.size() == 1) {
|
|
||||||
ZipEntryInfo entry = entries.get(0);
|
|
||||||
return !entry.isDirectory() && !entry.getName().contains("/");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 安全写入文件(带缓冲)
|
|
||||||
*/
|
|
||||||
private void writeFile(ZipInputStream zis, Path destPath) throws IOException {
|
|
||||||
try (BufferedOutputStream bos = new BufferedOutputStream(
|
|
||||||
Files.newOutputStream(destPath,
|
|
||||||
StandardOpenOption.CREATE,
|
|
||||||
StandardOpenOption.TRUNCATE_EXISTING
|
|
||||||
)
|
|
||||||
)) {
|
|
||||||
byte[] buffer = new byte[8192];
|
|
||||||
int len;
|
|
||||||
while ((len = zis.read(buffer)) > 0) {
|
|
||||||
bos.write(buffer, 0, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 内部类:存储 ZIP 条目信息
|
|
||||||
*/
|
|
||||||
private static class ZipEntryInfo {
|
|
||||||
private final String name;
|
|
||||||
private final boolean isDirectory;
|
|
||||||
|
|
||||||
public ZipEntryInfo(String name, boolean isDirectory) {
|
|
||||||
this.name = name;
|
|
||||||
this.isDirectory = isDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDirectory() {
|
|
||||||
return isDirectory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//上面这个方法会覆盖 TODO
|
//上面这个方法会覆盖 TODO
|
||||||
@ -4034,7 +3831,11 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
LOGGER.info("查询minio树的时候" + path);
|
LOGGER.info("查询minio树的时候" + path);
|
||||||
AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey("minio");
|
AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey("minio");
|
||||||
FileItemResult fileItemResult = fileService.getFileItem(path);
|
FileItemResult fileItemResult = fileService.getFileItem(path);
|
||||||
if (fileItemResult != null || fileItemResult.getName() != null) {
|
if (fileItemResult == null) {
|
||||||
|
dto.setUrl(null);
|
||||||
|
dto.setType(null);
|
||||||
|
|
||||||
|
}else{
|
||||||
dto.setUrl(fileItemResult.getUrl());
|
dto.setUrl(fileItemResult.getUrl());
|
||||||
//如果是压缩文件 类型就给zip
|
//如果是压缩文件 类型就给zip
|
||||||
boolean isValid = hasValidExtension(fileItemResult.getName(), sysDictionaryItems);
|
boolean isValid = hasValidExtension(fileItemResult.getName(), sysDictionaryItems);
|
||||||
@ -4130,6 +3931,8 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 封装发送数据的逻辑
|
// 封装发送数据的逻辑
|
||||||
private void sendData(String token, String[] values, int lineCount) {
|
private void sendData(String token, String[] values, int lineCount) {
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
@ -4164,6 +3967,81 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
private String getValueSafely(String[] values, int index, String defaultValue) {
|
private String getValueSafely(String[] values, int index, String defaultValue) {
|
||||||
return (index < values.length) ? values[index] : defaultValue;
|
return (index < values.length) ? values[index] : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文件内容接口
|
||||||
|
*
|
||||||
|
* @param id 文件的id
|
||||||
|
* @return 文件内容的纯文本(UTF-8 编码)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String readFileContent(String id) throws IOException {
|
||||||
|
StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper<StorageSourceConfig>().eq("name", "filePath"));
|
||||||
|
TsFiles tsFiles = tsFilesMapper.selectById(id);
|
||||||
|
// 1. 路径标准化与安全校验
|
||||||
|
Path targetPath = validateAndNormalizePath(config.getValue()+tsFiles.getWorkPath()+tsFiles.getFileName());
|
||||||
|
|
||||||
|
StringBuilder content = new StringBuilder();
|
||||||
|
|
||||||
|
// 使用缓冲流读取大文件(减少内存占用)
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(targetPath, StandardCharsets.UTF_8)) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
content.append(line).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content.toString().trim(); // 移除末尾多余换行
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证路径是否合法并转换为标准化路径
|
||||||
|
*/
|
||||||
|
private Path validateAndNormalizePath(String filePath) throws SecurityException {
|
||||||
|
StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper<StorageSourceConfig>().eq("name", "filePath"));
|
||||||
|
|
||||||
|
Path targetPath = Paths.get(filePath).normalize();
|
||||||
|
Path baseDirPath = Paths.get(config.getValue());
|
||||||
|
|
||||||
|
if (!targetPath.startsWith(baseDirPath)) {
|
||||||
|
throw new SecurityException("路径越界: " + targetPath);
|
||||||
|
}
|
||||||
|
// 防止路径遍历攻击(如 workPath 包含 "..")
|
||||||
|
if (targetPath.toString().contains("..")) {
|
||||||
|
throw new SecurityException("非法路径: " + targetPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存文件内容
|
||||||
|
*
|
||||||
|
* @param id 文件的id
|
||||||
|
* @param content 新的文件内容(HTML/文本)
|
||||||
|
* @throws SecurityException 路径非法时抛出
|
||||||
|
* @throws IOException 文件写入失败时抛出
|
||||||
|
*/
|
||||||
|
public void saveFileContent(String id, String content) throws SecurityException, IOException {
|
||||||
|
StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper<StorageSourceConfig>().eq("name", "filePath"));
|
||||||
|
TsFiles tsFiles = tsFilesMapper.selectById(id);
|
||||||
|
// 1. 路径标准化与安全校验
|
||||||
|
Path targetPath = validateAndNormalizePath(config.getValue()+tsFiles.getWorkPath()+tsFiles.getFileName());
|
||||||
|
// 2. 确保父目录存在
|
||||||
|
Path parentDir = targetPath.getParent();
|
||||||
|
if (parentDir != null && !Files.exists(parentDir)) {
|
||||||
|
Files.createDirectories(parentDir);
|
||||||
|
}
|
||||||
|
// 3. 写入文件内容(UTF-8 编码)
|
||||||
|
// 使用缓冲流写入大文件(支持流式上传)
|
||||||
|
try (BufferedWriter writer = Files.newBufferedWriter(targetPath, StandardCharsets.UTF_8)) {
|
||||||
|
writer.write(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -273,7 +273,8 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
|
|||||||
//判断节点名称是否存在
|
//判断节点名称是否存在
|
||||||
QueryWrapper<TsNodes> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<TsNodes> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq("node_name", tsnodes.getNodeName());//名称
|
queryWrapper.eq("node_name", tsnodes.getNodeName());//名称
|
||||||
queryWrapper.eq("parent_id", tsnodes.getParentId());//父节点
|
queryWrapper.eq("task_id", tsnodes.getTaskId());//所属任务ID
|
||||||
|
queryWrapper.eq("parent_id", tsnodes.getParentId());//所属任务ID
|
||||||
int count = tsNodesMapper.selectCount(queryWrapper);
|
int count = tsNodesMapper.selectCount(queryWrapper);
|
||||||
// 大于0说明 区域名称重复
|
// 大于0说明 区域名称重复
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
|
@ -395,20 +395,30 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FileItemResult getFileItem(String pathAndName) {
|
public FileItemResult getFileItem(String pathAndName) {
|
||||||
String fileName = FileUtil.getName(pathAndName);
|
try {
|
||||||
String parentPath = StringUtils.getParentPath(pathAndName);
|
String fileName = FileUtil.getName(pathAndName);
|
||||||
|
String parentPath = StringUtils.getParentPath(pathAndName);
|
||||||
|
String trimStartPath = StringUtils.concatTrimStartSlashes(param.getBasePath(), pathAndName);
|
||||||
|
|
||||||
String trimStartPath = StringUtils.concatTrimStartSlashes(param.getBasePath(), pathAndName);
|
// 可能抛出异常的代码包裹在try块中
|
||||||
ObjectMetadata objectMetadata = s3Client.getObjectMetadata(param.getBucketName(), trimStartPath);
|
ObjectMetadata objectMetadata;
|
||||||
|
try {
|
||||||
|
objectMetadata = s3Client.getObjectMetadata(param.getBucketName(), trimStartPath);
|
||||||
|
} catch (Exception e) { // 捕获所有类型的异常
|
||||||
|
return null; // 任何异常都返回null
|
||||||
|
}
|
||||||
|
|
||||||
FileItemResult fileItemResult = new FileItemResult();
|
FileItemResult fileItemResult = new FileItemResult();
|
||||||
fileItemResult.setName(fileName);
|
fileItemResult.setName(fileName);
|
||||||
fileItemResult.setSize(objectMetadata.getInstanceLength());
|
fileItemResult.setSize(objectMetadata.getInstanceLength());
|
||||||
fileItemResult.setTime(objectMetadata.getLastModified());
|
fileItemResult.setTime(objectMetadata.getLastModified());
|
||||||
fileItemResult.setType(FileTypeEnum.FILE);
|
fileItemResult.setType(FileTypeEnum.FILE);
|
||||||
fileItemResult.setPath(parentPath);
|
fileItemResult.setPath(parentPath);
|
||||||
fileItemResult.setUrl(getDownloadUrl(pathAndName));
|
fileItemResult.setUrl(getDownloadUrl(pathAndName));
|
||||||
return fileItemResult;
|
return fileItemResult;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null; // 其他代码异常也返回null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
Reference in New Issue
Block a user