package com.yfd.platform.modules.experimentalData.service.impl;

import cn.hutool.core.collection.CollUtil;


import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFilePermissions;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
import com.amazonaws.util.IOUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
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.component.TaskStatusHolder;
import com.yfd.platform.component.WebSocketServer;
import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.modules.experimentalData.domain.*;
import com.yfd.platform.modules.experimentalData.mapper.TsFilesMapper;
import com.yfd.platform.modules.experimentalData.mapper.TsNodesMapper;
import com.yfd.platform.modules.experimentalData.mapper.TsTaskMapper;
import com.yfd.platform.modules.experimentalData.service.ITsFilesService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yfd.platform.modules.storage.context.StorageSourceContext;
import com.yfd.platform.modules.storage.mapper.StorageSourceConfigMapper;
import com.yfd.platform.modules.storage.mapper.StorageSourceMapper;
import com.yfd.platform.modules.storage.model.entity.StorageSource;
import com.yfd.platform.modules.storage.model.entity.StorageSourceConfig;
import com.yfd.platform.modules.storage.model.enums.FileTypeEnum;
import com.yfd.platform.modules.storage.model.request.BatchDeleteRequest;
import com.yfd.platform.modules.storage.model.request.NewFolderRequest;
import com.yfd.platform.modules.storage.model.request.RenameFileRequest;
import com.yfd.platform.modules.storage.model.result.FileItemResult;
import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService;
import com.yfd.platform.system.domain.LoginUser;
import com.yfd.platform.system.domain.SysDictionaryItems;
import com.yfd.platform.system.mapper.SysDictionaryItemsMapper;
import com.yfd.platform.task.TaskMessage;
import com.yfd.platform.utils.StringUtils;
import io.netty.channel.ChannelInboundHandlerAdapter;
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.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.*;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.zip.*;

import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.codec.binary.Hex;


/**
 * <p>
 * 试验任务文档表 服务实现类
 * </p>
 *
 * @author LiMengNan
 * @since 2025-01-21
 */
@Service
public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> implements ITsFilesService {
    private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);


    private int time = 1;

    //试验任务文档表 Mapper
    @Resource
    private TsFilesMapper tsFilesMapper;

    @Resource
    private StorageSourceContext storageSourceContext;

    @Resource
    private StorageSourceConfigMapper storageSourceConfigMapper;

    @Resource
    private StorageSourceMapper storageSourceMapper;

    @Resource
    private SysDictionaryItemsMapper sysDictionaryItemsMapper;
    // 全局变量，用于存储当前任务的 Future
    private Future<?> currentTaskFuture;

    private final ExecutorService executorService = Executors.newSingleThreadExecutor();

    @Resource
    private TsTaskMapper tsTaskMapper;

    @Resource
    private TsNodesMapper tsNodesMapper;

    // 从数据库获取的压缩类型列表
    private List<String> compressSuffixes;
    private final Set<String> addedEntries = new HashSet<>();

    @Autowired
    private TaskStatusHolder taskStatusHolder;


    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    private TaskMessage taskMessage;

    /**********************************
     * 用途说明: 分页查询试验数据管理-文档内容
     * 参数说明
     *id            id
     * fileName      文件名称
     * startDate     (开始日期)
     * endDate      （结束日期）
     * keywords      关键字
     *nodeId         节点ID
     *taskId          任务ID
     *isFile          文件夹、文件区分
     * pageNum 当前页
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果
     ***********************************/
    @Override
    public Page<TsFiles> getTsFilesPage(String id, String fileName, String startDate, String endDate, String keywords, String nodeId, String taskId, String fileName1, String childNode, Page<TsFiles> page) throws Exception {
        //查询字典表获取压缩文件后缀
        QueryWrapper<SysDictionaryItems> queryWrapperSysDictionary = new QueryWrapper<>();
        queryWrapperSysDictionary.eq("parentcode", "compressType");
        queryWrapperSysDictionary.orderByAsc("orderno");
        List<SysDictionaryItems> sysDictionaryItems = sysDictionaryItemsMapper.selectList(queryWrapperSysDictionary);


        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        // 固定条件过滤
        if (StringUtils.isNotBlank(nodeId)) {
            queryWrapper.eq("node_id", nodeId);
        }
        if (StringUtils.isNotBlank(taskId)) {
            queryWrapper.eq("task_id", taskId);
        }


        // 模糊搜索
        if (StringUtils.isNotBlank(fileName)) {
            queryWrapper.like("file_name", fileName);
        }
        if (StringUtils.isNotBlank(keywords)) {
            queryWrapper.like("keywords", keywords);
        }

        // 时间范围过滤
        if (StringUtils.isNotBlank(startDate)) {
            queryWrapper.ge("upload_time", startDate);
        }
        if (StringUtils.isNotBlank(endDate)) {
            queryWrapper.le("upload_time", endDate);
        }
        boolean hasSearchCondition = StringUtils.isNotBlank(fileName) || StringUtils.isNotBlank(keywords) || StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate);

        // 处理层级关系条件
        if (StringUtils.isNotBlank(id)) {
            // 有ID时：根据childNode决定是否递归子节点   如果filename
            if (childNode != null && childNode.equals("0") && hasSearchCondition) {
                List<String> childIds = getAllChildIds(id);
                queryWrapper.in("parent_id", childIds);
            } else {
                // 其他情况：childNode=1 或 无搜索条件时 按当前层级查询
                queryWrapper.eq("parent_id", id);
            }
        } else {
            //如果说ID是空 说明是顶级节点
            if (childNode != null) {
                if (childNode.equals("0")) {
                    if (hasSearchCondition) {
                        // 递归获取根目录下所有子节点ID（包含多级）
                        List<String> allChildIds = getAllChildIds("00"); // 注意这里传入字符串"00"
                        allChildIds.add("00"); // 包含根目录的直接子节点
                        queryWrapper.in("parent_id", allChildIds);
                    } else {
                        // 无搜索条件时，仅查询根目录的直接子节点
                        queryWrapper.eq("parent_id", "00");
                    }
                } else if (childNode.equals("1")) {
                    // 始终只查根目录的直接子节点
                    queryWrapper.eq("parent_id", "00");
                }
            }

//            // 无ID时：childNode=1则查根目录，childNode=0查全表
//            if (childNode != null && childNode.equals("1")) {
//                queryWrapper.eq("parent_id", "00");
//            }
        }
        //
        queryWrapper.isNotNull("work_path");
        queryWrapper.ne("work_path", "");
        //文件还是文件夹
        queryWrapper.orderByDesc("is_file");
        //文件名称
        queryWrapper.orderByDesc("file_name");
        //分页查询
        Page<TsFiles> tsFilesPage = tsFilesMapper.selectPage(page, queryWrapper);
        if (tsFilesPage == null) {
            tsFilesPage = new Page<>();
            tsFilesPage.setRecords(new ArrayList<>()); // 确保 records 被初始化
        }
        List<TsFiles> records = tsFilesPage.getRecords();
        for (TsFiles tsFiles : records) {

            String fileNameData = tsFiles.getFileName();
            //主要是用于文件路径加名称
            String path = tsFiles.getWorkPath() + fileNameData;
            //准备获取文件的信息
            AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey("local");
            FileItemResult fileItemResult = fileService.getFileItem(path);
            if (fileItemResult == null || fileItemResult.getName() == null) {
                LOGGER.error("{}文件没有上传到工作空间，请重新选择上传", fileNameData);
            }
            tsFiles.setUrl(fileItemResult.getUrl());
            //如果是压缩文件 类型就给zip
            boolean isValid = hasValidExtension(fileItemResult.getName(), sysDictionaryItems);
            if (isValid) {
                tsFiles.setType("ZIP");
            } else {
                tsFiles.setType(fileItemResult.getType().getValue());
            }
            if (tsFiles.getUpdateTime() == null) {
                tsFiles.setUpdateTime(tsFiles.getUploadTime());
            }
//            String ProcessingPath = processingPath(tsFiles.getWorkPath(), nodeId);
//            tsFiles.setWorkPath(ProcessingPath);
        }


        tsFilesPage.setRecords(records); // 同步到 tsFilesPage
        int currentPage = (int) page.getCurrent();
        // 如果是前五页，将结果存入Redis（有效期建议30分钟）
        if (!StrUtil.isEmpty(id)) {
            if (currentPage >= 1 && currentPage <= 5) {
                String redisKey = "tsfiles_" + taskId + "_" + nodeId + "_parentId" + id + "_page_" + currentPage + "";
                redisTemplate.opsForValue().set(redisKey, tsFilesPage, 2, TimeUnit.HOURS);
            }
        } else {
            String redisKey = "tsfiles_" + taskId + "_" + nodeId + "_page_" + currentPage;
            redisTemplate.delete(redisKey);
        }
        return tsFilesPage;

    }

    /**
     * Redis缓存获取
     *
     * @param taskId
     * @param nodeId
     * @param currentPage
     * @return
     */
    @Override
    public IPage<TsFiles> getCachedTsFilesPage(String taskId, String nodeId, int currentPage, String id) {
        String redisKey = "tsfiles_" + taskId + "_" + nodeId + "_parentId" + id + "_page_" + currentPage;
        Object data = redisTemplate.opsForValue().get(redisKey);
        try {
            if (data instanceof IPage<?>) {
                @SuppressWarnings("unchecked")
                IPage<TsFiles> page = (IPage<TsFiles>) data;

                // 类型校验
                if (isValidPage(page)) {
                    return page;
                }
            }
        } catch (Exception e) {
            // 记录异常日志（关键！）
            LOGGER.error("反序列化失败，删除缓存: {}", redisKey, e);

            // 异步删除缓存，避免阻塞主流程
            CompletableFuture.runAsync(() -> redisTemplate.delete(redisKey));
        }

        return null; // 或返回空分页对象 new Page<>(0)

//        if (data instanceof IPage<?>) {
//            // 由于启用了类型信息，可以直接强制转换
//            @SuppressWarnings("unchecked")
//            IPage<TsFiles> page = (IPage<TsFiles>) data;
//            // 检查 records 是否已正确反序列化
//            if (page.getRecords() != null && !page.getRecords().isEmpty()
//                    && page.getRecords().get(0) instanceof TsFiles) {
//                return page;
//            } else {
//                // 处理可能的反序列化异常
//                throw new IllegalStateException("反序列化失败，records 类型不正确");
//            }
//        }
//        return null;
    }


    private boolean isValidPage(IPage<TsFiles> page) {
        return page.getRecords() != null
                && !page.getRecords().isEmpty()
                && page.getRecords().get(0) instanceof TsFiles;
    }

    public boolean hasValidExtension(String name, List<SysDictionaryItems> sysDictionaryItems) {
        // 如果传入的文件名为空，返回 false
        if (name == null) {
            return false;
        }
        // 如果传入的字典表集合为空，返回 false
        if (sysDictionaryItems == null || sysDictionaryItems.isEmpty()) {
            return false;
        }
        // 获取文件的扩展名
        String fileExtension = getFileExtension(name);
        for (SysDictionaryItems sysDictionaryItems1 : sysDictionaryItems) {
            String dictName = "." + sysDictionaryItems1.getDictName();
            if (fileExtension.equalsIgnoreCase(dictName)) {
                // 如果文件扩展名与字典表中的扩展名匹配，返回 true
                return true;
            }
        }
        // 如果没有匹配的扩展名，返回 false
        return false;
    }

    // 获取文件扩展名的方法
    private String getFileExtension(String filename) {

        if (filename.endsWith(".tar.gz")) {
            return ".tar.gz";
        }
        // 检查是否是 .tar 格式
        if (filename.endsWith(".tar")) {
            return ".tar";  // 去掉 ".tar"
        }
        // 检查是否是 .zip 格式
        if (filename.endsWith(".zip")) {
            return ".zip";  // 去掉 ".zip"
        }
        // 检查是否是 .7z 格式
        if (filename.endsWith(".7z")) {
            return ".7z";  // 去掉 ".7z"
        }
        if (filename != null && filename.contains(".")) {
            return filename.substring(filename.lastIndexOf("."));
        }
        return ""; // 如果没有扩展名，返回空字符串
    }

    // 递归获取所有子节点ID（包含隔代子节点）
    private List<String> getAllChildIds(String parentId) {
        List<String> childIds = new ArrayList<>();
        List<TsFiles> children = tsFilesMapper.selectList(
                new QueryWrapper<TsFiles>().select("id").eq("parent_id", parentId).eq("is_file", "FOLDER") // 只遍历文件夹节点
        );
        for (TsFiles child : children) {
            childIds.add(child.getId());
            childIds.addAll(getAllChildIds(child.getId()));
        }
        return childIds;
    }

    /**********************************
     * 用途说明:   查询实验数据管理文件夹
     * 参数说明
     * pageNum 当前页
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果
     ***********************************/
    @Override
    public List<TsFiles> getsListTsFiles(String id, String path, String nodeId, String taskId) {
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("is_file", "FOLDER");
        if (StringUtils.isNotBlank(id)) {
            queryWrapper.eq("parent_id", id);
        }
        if (StringUtils.isNotBlank(path)) {
            queryWrapper.eq("work_path", path);
        }
        if (StringUtils.isNotBlank(nodeId)) {
            queryWrapper.eq("node_id", nodeId);
        }
        if (StringUtils.isNotBlank(taskId)) {
            queryWrapper.eq("task_id", taskId);
        }
        queryWrapper.isNotNull("work_path");
        List<TsFiles> tsFiles = tsFilesMapper.selectList(queryWrapper);

//        for (TsFiles tsFiles1 : tsFiles) {
//            String ProcessingPath = processingPath(tsFiles1.getWorkPath(), nodeId);
//            tsFiles1.setWorkPath(ProcessingPath);
//        }
        return tsFiles;
    }


    /***********************************
     * 用途说明：新增试验数据管理-文档内容
     * 参数说明
     * TsFiles     文档内容
     * 返回值说明:  com.yfd.platform.config.ResponseResult 返回新增成功或者失败
     ***********************************/
    @Override
    public ResponseResult addTsFiles(TsFiles tsFiles) {

        //todo 新增成功以后 删除redis
        for (int page = 1; page <= 5; page++) {
            String redisKey = "tsfiles_" + tsFiles.getTaskId() + "_" + tsFiles.getNodeId() + "_parentId" + tsFiles.getParentId() + "_page_" + page;


            redisTemplate.delete(redisKey);
        }
        LOGGER.info("已清理缓存：taskid={}, node={}, pages=1-5", tsFiles.getTaskId(), tsFiles.getNodeId());

        Boolean value = true;
        //文件名称和大小   因为支持多个上传所以用，分隔
        List<String> names = Arrays.asList(tsFiles.getFileName().split(","));
        List<String> sizes = Arrays.asList(tsFiles.getFileSize().split(","));
        //上传人是当前登录人
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = (LoginUser) authentication.getPrincipal();

        // 数据校验
        if (names.size() != sizes.size()) {
            LOGGER.error("文件名称和文件大小的列表长度不一致");
            return ResponseResult.error("文件名称和文件大小的列表长度不一致！");
        }

//
//        //判断文件夹是否创建
//        AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey("local");
//        boolean flag = fileService.isFolderCreated(File.separator + tsFiles.getNodeId());
        //如果是false 说明没有创建  那就新建一个文件夹
//        if(!flag){
//            //本地创建文件夹
//            NewFolderRequest newFolderRequest = new NewFolderRequest();
//            newFolderRequest.setStorageKey("local");
//            newFolderRequest.setPath(tsFiles.getWorkPath());
//            newFolderRequest.setPassword(null);
//            newFolderRequest.setName(tsFiles.getFileName());
//
//            AbstractBaseFileService<?> fileServiceData = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey());
//            boolean flagData = fileServiceData.newFolder(newFolderRequest.getPath(), newFolderRequest.getName());
//            if(!flagData){
//                LOGGER.error("创建节点文件夹失败！");
//                return ResponseResult.error("新增文件失败!");
//            }
//        }

        List<TsFiles> filesToSave = new ArrayList<>();
        // 设置当前时间
        LocalDateTime now = LocalDateTime.now();
        // 转换为 Timestamp
        Timestamp currentTime = Timestamp.valueOf(now);
        tsFiles.setUploadTime(currentTime);

        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i).trim();
            String sizeStr = sizes.get(i).trim();

            String pathAndName = tsFiles.getWorkPath() + name;
            //准备获取文件的信息
            AbstractBaseFileService<?> fileServicefile = storageSourceContext.getByStorageKey("local");
            FileItemResult fileItemResult = fileServicefile.getFileItem(pathAndName);
            if (fileItemResult == null || fileItemResult.getName() == null) {
                return ResponseResult.error(name + "文件没有上传到工作空间，请重新选择上传！");
            }

            //通过节点ID  任务ID   路径  上级ID 文件名称 查询是否重复
            QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("node_id", tsFiles.getNodeId());
            queryWrapper.eq("task_id", tsFiles.getTaskId());
            queryWrapper.eq("work_path", tsFiles.getWorkPath());
            queryWrapper.eq("parent_id", tsFiles.getParentId());
            queryWrapper.eq("file_name", name);
            int count = tsFilesMapper.selectCount(queryWrapper);
            // 大于0说明 区域名称重复
            if (count > 0) {
                return ResponseResult.error("文件已存在！");
            }
            // 校验文件大小是否可以转换成数值
            try {
                TsFiles files1 = new TsFiles();
                files1.setTaskId(tsFiles.getTaskId());
                files1.setIsFile(tsFiles.getIsFile());
                files1.setParentId(tsFiles.getParentId());
                files1.setNodeId(tsFiles.getNodeId());
                files1.setWorkPath(tsFiles.getWorkPath());
                files1.setBackupPath(tsFiles.getBackupPath());
                files1.setKeywords(tsFiles.getKeywords());
                files1.setDescription(tsFiles.getDescription());
                files1.setUploadTime(tsFiles.getUploadTime());
                files1.setUploader(loginuser.getUsername());
                files1.setFileName(name);
                if ("0.000".equals(sizeStr)) {
                    files1.setFileSize("0.001");

                } else {
                    files1.setFileSize(sizeStr);
                }

                filesToSave.add(files1);
            } catch (NumberFormatException e) {
                LOGGER.error("文件大小必须是有效的数字");
            }
        }
        if (filesToSave.size() > 0) {
            //循环新增
            for (TsFiles filess : filesToSave) {
                int valueAdded = tsFilesMapper.insert(filess);
                if (valueAdded == 1) {
                    LOGGER.info("表结构和本地都创建文件夹成功");
                    value = true;
                } else {
                    LOGGER.error("本地创建文件夹成功，表结构新增失败");
                    value = false;
                }
            }
        }
        if (value) {
            return ResponseResult.success("新增文件成功！");
        } else {
            return ResponseResult.error("新增文件失败！");
        }

    }


    /***********************************
     * 用途说明：新增试验数据管理-文件夹
     * 参数说明
     * TsFiles     文档内容
     * 返回值说明:  com.yfd.platform.config.ResponseResult 返回新增成功或者失败
     ***********************************/
    @Override
    @Transactional(rollbackFor = Exception.class)// 添加事务注解，遇到异常时回滚
    public ResponseResult addTsFile(TsFiles tsFiles) throws IOException {

        //todo 新增成功以后 删除redis
        for (int page = 1; page <= 5; page++) {
            String redisKey = "tsfiles_" + tsFiles.getTaskId() + "_" + tsFiles.getNodeId() + "_parentId" + tsFiles.getParentId() + "_page_" + page;
            redisTemplate.delete(redisKey);
        }
        LOGGER.info("已清理缓存：taskid={}, node={}, pages=1-5", tsFiles.getTaskId(), tsFiles.getNodeId());

        if (tsFiles.getIsFile().equals("FILE")) {
            StorageSourceConfig config = getStorageConfig("filePath", "local");
            String basePath = config.getValue() + tsFiles.getWorkPath();
            // 拼接完整的文件路径
            Path filePath = Paths.get(basePath, tsFiles.getFileName() + ".txt");

            // 确保目录存在，如果不存在则创建目录
            Files.createDirectories(filePath.getParent());

            // 使用 Files.write() 写入字符串内容
            Files.write(filePath, "默认内容".getBytes(StandardCharsets.UTF_8));
            tsFiles.setFileName(tsFiles.getFileName() + ".txt");
            UsernamePasswordAuthenticationToken authentication =
                    (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
            LoginUser loginuser = (LoginUser) authentication.getPrincipal();
            // 设置当前时间
            LocalDateTime now = LocalDateTime.now();
            // 转换为 Timestamp
            Timestamp currentTime = Timestamp.valueOf(now);
            tsFiles.setUploadTime(currentTime);
            tsFiles.setUploader(loginuser.getUsername());
            tsFiles.setFileSize("0.001");
            int valueAdded = tsFilesMapper.insert(tsFiles);
            if (valueAdded == 1) {
                LOGGER.info("文件创建成功");
                return ResponseResult.success();
            } else {
                LOGGER.error("文件创建失败");
                return ResponseResult.error();
            }
        } else {
            // 校验文件名是否包含非法字符
            String fileName = tsFiles.getFileName();
            if (containsInvalidCharacters(fileName)) {
                return ResponseResult.error("文件名包含非法字符（<>:\"/\\|?*）！");
            }

            //上传人是当前登录人
            UsernamePasswordAuthenticationToken authentication =
                    (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
            LoginUser loginuser = (LoginUser) authentication.getPrincipal();
            // 设置当前时间
            LocalDateTime now = LocalDateTime.now();
            // 转换为 Timestamp
            Timestamp currentTime = Timestamp.valueOf(now);
            tsFiles.setUploadTime(currentTime);
            tsFiles.setUploader(loginuser.getUsername());
            tsFiles.setFileSize("0");
            QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("node_id", tsFiles.getNodeId());
            queryWrapper.eq("task_id", tsFiles.getTaskId());
            queryWrapper.eq("parent_id", tsFiles.getParentId());
            queryWrapper.eq("file_name", tsFiles.getFileName());
            int count = tsFilesMapper.selectCount(queryWrapper);
            // 大于0说明 区域名称重复
            if (count > 0) {
                return ResponseResult.error("文件夹名称已存在！");
            }
            //本地创建文件夹
            NewFolderRequest newFolderRequest = new NewFolderRequest();
            newFolderRequest.setStorageKey("local");
            newFolderRequest.setPath(tsFiles.getWorkPath());
            newFolderRequest.setPassword(null);
            newFolderRequest.setName(tsFiles.getFileName());

            AbstractBaseFileService<?> fileServicefolder = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey());
            boolean flagfolder = fileServicefolder.newFolder(newFolderRequest.getPath(), newFolderRequest.getName());
            if (flagfolder) {
//            tsFiles.setWorkPath(File.separator + tsFiles.getNodeId() + tsFiles.getWorkPath());
                int valueAdded = tsFilesMapper.insert(tsFiles);
                if (valueAdded == 1) {
                    LOGGER.info("表结构和本地都创建文件夹成功");
                    return ResponseResult.success();
                } else {
                    LOGGER.error("本地创建文件夹成功，表结构新增失败");
                    return ResponseResult.error();
                }
            } else {
                LOGGER.error("本地创建文件夹失败");
                return ResponseResult.error();
            }
        }
    }

    // 校验文件名是否包含非法字符
    private boolean containsInvalidCharacters(String fileName) {
        // 定义非法字符的正则表达式
        String regex = "[<>:\"/\\\\|?*]";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(fileName);
        return matcher.find();
    }

    /**********************************
     * 用途说明: 修改试验数据管理-文档内容
     * 参数说明
     * TsFiles     文档内容
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败
     ***********************************/
    @Override
    @Transactional(rollbackFor = Exception.class)// 添加事务注解，遇到异常时回滚
    public ResponseResult updateTsFiles(TsFiles tsFiles) {

        //todo 新增成功以后 删除redis
        for (int page = 1; page <= 5; page++) {
            String redisKey = "tsfiles_" + tsFiles.getTaskId() + "_" + tsFiles.getNodeId() + "_parentId" + tsFiles.getParentId() + "_page_" + page;
            redisTemplate.delete(redisKey);
        }
        LOGGER.info("已清理缓存：taskid={}, node={}, pages=1-5", tsFiles.getTaskId(), tsFiles.getNodeId());
        // 校验文件名是否包含非法字符
        String fileName = tsFiles.getFileName();
        if (containsInvalidCharacters(fileName)) {
            return ResponseResult.error("文件名包含非法字符（<>:\"/\\|?*）！");
        }

        // 修改之前查询表中的文件名是否修改，如果发生变动先修改 minio 然后再修改表结构
        TsFiles filesData = tsFilesMapper.selectById(tsFiles.getId());

        // 判断文件名是否修改  不一致需要修改
        if (!tsFiles.getFileName().equals(filesData.getFileName())) {

            //校验文件夹会不会重命名
            if ("FOLDER".equals(tsFiles.getIsFile())) {
                //如果是文件夹 递归修改它下面的所有文件的路径
                QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("node_id", tsFiles.getNodeId());
                queryWrapper.eq("task_id", tsFiles.getTaskId());
                queryWrapper.eq("work_path", tsFiles.getWorkPath());
                queryWrapper.eq("parent_id", tsFiles.getParentId());
                queryWrapper.eq("file_name", tsFiles.getFileName());
                int count = tsFilesMapper.selectCount(queryWrapper);
                // 大于0说明 区域名称重复
                if (count > 0) {
                    return ResponseResult.error("文件夹名称已存在！");
                }

                //查询数据源
                RenameFileRequest renameFileRequest = new RenameFileRequest();
                renameFileRequest.setName(filesData.getFileName());
                renameFileRequest.setNewName(tsFiles.getFileName());
                renameFileRequest.setPassword("");
                renameFileRequest.setPath(filesData.getWorkPath());
                renameFileRequest.setStorageKey("local");
                AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(renameFileRequest.getStorageKey());
                Boolean flag = fileService.renameFile(renameFileRequest.getPath(), renameFileRequest.getName(), renameFileRequest.getNewName());
                //如果是true  说明至少有一个修改了文件夹
                if (flag) {
                    //递归下面所有的文件文件夹  路径都需要修改
                    List<TsFiles> resultList = new ArrayList<>();

                    if (tsFiles.getBackupPath() != null && tsFiles.getBackupPath() != "") {
                        // 设置当前时间
                        LocalDateTime now = LocalDateTime.now();
                        // 转换为 Timestamp
                        Timestamp currentTime = Timestamp.valueOf(now);
                        //新通过id找到表中现有的 修改也是修改表中现有的数据   新增的一条就是重命名以后的  所有备份空间路径为空    原来的哪条数据把工作空间路径改成空
                        TsFiles tsFiles2 = tsFilesMapper.selectById(tsFiles.getId());//查询原来的数据   把原来的数据
                        tsFiles2.setWorkPath("");

                        TsFiles tsFiles1 = new TsFiles();
                        tsFiles1.setNodeId(tsFiles.getNodeId());
                        tsFiles1.setTaskId(tsFiles.getTaskId());
                        tsFiles1.setIsFile(tsFiles.getIsFile());
                        tsFiles1.setParentId(tsFiles.getParentId());
                        tsFiles1.setFileName(tsFiles.getFileName());
                        tsFiles1.setFileSize(tsFiles.getFileSize());
                        tsFiles1.setWorkPath(tsFiles.getWorkPath());
                        tsFiles1.setBackupPath("");
                        tsFiles1.setKeywords(tsFiles.getKeywords());
                        tsFiles1.setDescription(tsFiles.getDescription());
                        tsFiles1.setUploadTime(currentTime);
                        tsFiles1.setUploader(tsFiles.getUploader());
                        tsFiles1.setUpdateTime(currentTime);
                        int valueInsert = tsFilesMapper.insert(tsFiles1);
                        if (valueInsert == 1) {
                            int valueUpdate = tsFilesMapper.updateById(tsFiles2);
                            // 获取当前文件夹的完整路径（如 "/147/222/"）
                            String oldFullPath = filesData.getWorkPath() + filesData.getFileName() + "/";
                            String newFullPath = filesData.getWorkPath() + tsFiles.getFileName() + "/";
                            querySubFilesAndUpdatePaths(resultList, tsFiles2.getId(), oldFullPath, newFullPath, tsFiles1.getId());
                            if (valueUpdate == 1) {
                                LOGGER.info("local和minio修改成功，表结构修改成功");
                                return ResponseResult.success();
                            } else {
                                LOGGER.error("local和minio修改成功，表结构修改失败");
                                return ResponseResult.error();
                            }
                        } else {
                            LOGGER.error("local和minio修改成功，表结构修改失败");
                            return ResponseResult.error();
                        }


                    } else {
                        // 如果备份空间为空  直接修改数据库
                        String oldFullPath = filesData.getWorkPath() + filesData.getFileName() + "/";
                        String newFullPath = filesData.getWorkPath() + tsFiles.getFileName() + "/";
                        querySubFilesAndUpdatePaths(resultList, tsFiles.getId(), oldFullPath, newFullPath, tsFiles.getId());

                        int valueUpdate = tsFilesMapper.updateById(tsFiles);
                        if (valueUpdate == 1) {
                            LOGGER.info("local和minio修改成功，表结构修改成功");
                            return ResponseResult.success();
                        } else {
                            LOGGER.error("local和minio修改成功，表结构修改失败");
                            return ResponseResult.error();
                        }
                    }
                } else {
                    LOGGER.error("local和minio修改失败");
                    return ResponseResult.error();
                }
            } else {
                //如果是文件   直接修改就可以
                QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("node_id", tsFiles.getNodeId());
                queryWrapper.eq("task_id", tsFiles.getTaskId());
                queryWrapper.eq("work_path", tsFiles.getWorkPath());
                queryWrapper.eq("parent_id", tsFiles.getParentId());
                queryWrapper.eq("file_name", tsFiles.getFileName());
                int count = tsFilesMapper.selectCount(queryWrapper);
                // 大于0说明 区域名称重复
                if (count > 0) {
                    return ResponseResult.error("文件名称已存在！");
                }
                //查询数据源
                RenameFileRequest renameFileRequest = new RenameFileRequest();
                renameFileRequest.setName(filesData.getFileName());
                renameFileRequest.setNewName(tsFiles.getFileName());
                renameFileRequest.setPassword("");
                renameFileRequest.setPath(filesData.getWorkPath());
                renameFileRequest.setStorageKey("local");
                AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(renameFileRequest.getStorageKey());
                Boolean flag = fileService.renameFile(renameFileRequest.getPath(), renameFileRequest.getName(), renameFileRequest.getNewName());
                //如果是true  说明至少有一个修改了文件
                if (flag) {
                    //如果备份路径不为空  那么就先增加一条  然后修改本地的
                    if (tsFiles.getBackupPath() != null && tsFiles.getBackupPath() != "") {
                        // 设置当前时间
                        LocalDateTime now = LocalDateTime.now();
                        // 转换为 Timestamp
                        Timestamp currentTime = Timestamp.valueOf(now);
                        //新通过id找到表中现有的 修改也是修改表中现有的数据   新增的一条就是重命名以后的  所有备份空间路径为空    原来的哪条数据把工作空间路径改成空
                        TsFiles tsFiles2 = tsFilesMapper.selectById(tsFiles.getId());//查询原来的数据   把原来的数据
                        tsFiles2.setWorkPath("");

                        TsFiles tsFiles1 = new TsFiles();
                        tsFiles1.setNodeId(tsFiles.getNodeId());
                        tsFiles1.setTaskId(tsFiles.getTaskId());
                        tsFiles1.setIsFile(tsFiles.getIsFile());
                        tsFiles1.setParentId(tsFiles.getParentId());
                        tsFiles1.setFileName(tsFiles.getFileName());
                        tsFiles1.setFileSize(tsFiles.getFileSize());
                        tsFiles1.setWorkPath(tsFiles.getWorkPath());
                        tsFiles1.setBackupPath("");
                        tsFiles1.setKeywords(tsFiles.getKeywords());
                        tsFiles1.setDescription(tsFiles.getDescription());
                        tsFiles1.setUploadTime(currentTime);
                        tsFiles1.setUploader(tsFiles.getUploader());
                        tsFiles1.setUpdateTime(currentTime);
                        int valueInsert = tsFilesMapper.insert(tsFiles1);
                        if (valueInsert == 1) {
                            int valueUpdate = tsFilesMapper.updateById(tsFiles2);
                            if (valueUpdate == 1) {
                                LOGGER.info("local和minio修改成功，表结构修改成功");
                                return ResponseResult.success();
                            } else {
                                LOGGER.error("local和minio修改成功，表结构修改失败");
                                return ResponseResult.error();
                            }
                        } else {
                            LOGGER.error("local和minio修改成功，表结构修改失败");
                            return ResponseResult.error();
                        }


                    } else {
                        // 如果备份空间为空  直接修改数据库
                        int valueUpdate = tsFilesMapper.updateById(tsFiles);
                        if (valueUpdate == 1) {
                            LOGGER.info("local和minio修改成功，表结构修改成功");
                            return ResponseResult.success();
                        } else {
                            LOGGER.error("local和minio修改成功，表结构修改失败");
                            return ResponseResult.error();
                        }
                    }


                } else {
                    LOGGER.error("local和minio修改失败");
                    return ResponseResult.error();
                }
            }
        } else {
            // 如果文件名没有修改，仅更新数据库
            int valueUpdate = tsFilesMapper.updateById(tsFiles);
            if (valueUpdate == 1) {
                return ResponseResult.success();
            } else {
                return ResponseResult.error();
            }
        }
    }

    //
    private void querySubFilesAndUpdatePaths(List<TsFiles> resultList, String id, String oldName, String newName, String newid) {
        // 构造 QueryWrapper 查询当前文件夹下的文件
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", id);
        // 查询当前文件夹下的所有子项
        List<TsFiles> subFiles = tsFilesMapper.selectList(queryWrapper);
        // 确保 subFiles 不为空
        if (subFiles == null || subFiles.isEmpty()) {
            return;
        }

        // 将查询到的结果添加到结果列表
        resultList.addAll(subFiles);

        // 遍历每个子项，更新路径并递归查询其子项
        for (TsFiles subFile : subFiles) {
            String oldWorkPath = subFile.getWorkPath();
            // 使用正则表达式替换目标文件夹名称
            String newWorkPath = oldWorkPath.replaceAll(oldName, newName);

            //判断备份空间路径是否为空   如果不为
            if (subFile.getBackupPath() != null && subFile.getBackupPath() != "") {
                // 设置当前时间
                LocalDateTime now = LocalDateTime.now();
                // 转换为 Timestamp
                Timestamp currentTime = Timestamp.valueOf(now);
                //新通过id找到表中现有的 修改也是修改表中现有的数据   新增的一条就是重命名以后的  所有备份空间路径为空    原来的哪条数据把工作空间路径改成空
                TsFiles tsFiles2 = tsFilesMapper.selectById(subFile.getId());//查询原来的数据   把原来的数据
                tsFiles2.setWorkPath("");

                TsFiles tsFiles1 = new TsFiles();
                tsFiles1.setNodeId(subFile.getNodeId());
                tsFiles1.setTaskId(subFile.getTaskId());
                tsFiles1.setIsFile(subFile.getIsFile());
                tsFiles1.setParentId(newid);
                tsFiles1.setFileName(subFile.getFileName());
                tsFiles1.setFileSize(subFile.getFileSize());
                tsFiles1.setWorkPath(newWorkPath);
                tsFiles1.setBackupPath("");
                tsFiles1.setKeywords(subFile.getKeywords());
                tsFiles1.setDescription(subFile.getDescription());
                tsFiles1.setUploadTime(currentTime);
                tsFiles1.setUploader(subFile.getUploader());
                tsFiles1.setUpdateTime(currentTime);
                int valueInsert = tsFilesMapper.insert(tsFiles1);
                if (valueInsert == 1) {
                    int valueUpdate = tsFilesMapper.updateById(tsFiles2);
                    if (valueUpdate == 1) {
                        if ("FOLDER".equals(subFile.getIsFile())) {
                            querySubFilesAndUpdatePaths(resultList, tsFiles2.getId(), oldName, newName, tsFiles1.getId());
                        }
                    }
                }

            } else {
                // 更新数据库中的路径
                subFile.setWorkPath(newWorkPath);
                subFile.setParentId(newid);
                tsFilesMapper.updateById(subFile);
                // 递归查询每个子项的子文件
                if ("FOLDER".equals(subFile.getIsFile())) {
                    querySubFilesAndUpdatePaths(resultList, subFile.getId(), oldName, newName, subFile.getId());
                }
            }
        }
    }

    /**********************************
     * 用途说明: 批量删除试验数据管理-文档内容
     * 参数说明  id 文档内容ID
     * 参数说明  type local还是minio
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败
     ***********************************/
    @Override
    @Transactional(rollbackFor = Exception.class)// 添加事务注解，遇到异常时回滚
    public String deleteTsFilesByIds(List<String> dataset, String type) {
        List<TsFiles> filesList = tsFilesMapper.selectBatchIds(dataset);


        int LocalSuccessCount = 0, LocalFailCount = 0, Localtotal = CollUtil.size(dataset);
        //Todo  最直接的办法   循环出来 一条一条删除
        for (TsFiles files : filesList) {
            //判断是文件还是文件夹
            if ("FOLDER".equals(files.getIsFile())) {
                //如果是文件夹
                List<BatchDeleteRequest.DeleteItem> deleteItemList = new ArrayList<>();
                BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem();
                deleteItemData.setName(files.getFileName());
                deleteItemData.setPassword("");
                if ("local".equals(type)) {
                    deleteItemData.setPath(files.getWorkPath());
                } else {
                    deleteItemData.setPath(files.getBackupPath());
                }
                deleteItemData.setType(FileTypeEnum.FOLDER);
                deleteItemList.add(deleteItemData);
                //首先通过ID集合查询所有的内容  然后放到List<DeleteItem> deleteItems里面  放好以后删除数据库  然后删除minio
                BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest();
                batchDeleteRequest.setDeleteItems(deleteItemList);
                batchDeleteRequest.setStorageKey(type);

                AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey());
                List<BatchDeleteRequest.DeleteItem> deleteItems = batchDeleteRequest.getDeleteItems();

                int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItems);
                for (BatchDeleteRequest.DeleteItem deleteItem : deleteItems) {

                    boolean flag = false;
                    try {
                        if (deleteItem.getType() == FileTypeEnum.FILE) {
                            flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName());
                        } else if (deleteItem.getType() == FileTypeEnum.FOLDER) {
                            flag = fileService.deleteFolder(deleteItem.getPath(), deleteItem.getName());
                        }

                        if (flag) {
                            deleteSuccessCount++;
                        } else {
                            deleteFailCount++;
                        }
                    } catch (Exception e) {
                        LOGGER.error("删除文件/文件夹失败, 文件路径: {}, 文件名称: {}", deleteItem.getPath(), deleteItem.getName(), e);
                        deleteFailCount++;
                    }
                }

                if (totalCount > 1) {
                    //return ResponseResult.success("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个.");
                    LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个.");
                } else {
                    //return totalCount == deleteSuccessCount ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败");
                    LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个.");
                }

                //如果是1 说明成功删除
                if (deleteSuccessCount >= 1) {
                    int valueDelete = deleteFolder(files.getId(), type);
                    if (valueDelete > 0) {
                        LocalSuccessCount = valueDelete;
                        LOGGER.info("表结构成功删除");
                    } else {
                        System.out.println("没有记录被删除");
                    }
                } else {
                    LocalFailCount++;
                }
            } else {
                //如果是文件

                List<BatchDeleteRequest.DeleteItem> deleteItemList = new ArrayList<>();
                BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem();
                deleteItemData.setName(files.getFileName());
                deleteItemData.setPassword("");
                if ("local".equals(type)) {
                    deleteItemData.setPath(files.getWorkPath());
                } else {
                    deleteItemData.setPath(files.getBackupPath());
                }
                deleteItemData.setType(FileTypeEnum.FILE);
                deleteItemList.add(deleteItemData);
                //首先通过ID集合查询所有的内容  然后放到List<DeleteItem> deleteItems里面  放好以后删除数据库  然后删除minio
                BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest();
                batchDeleteRequest.setDeleteItems(deleteItemList);
                batchDeleteRequest.setStorageKey(type);

                AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey());
                List<BatchDeleteRequest.DeleteItem> deleteItems = batchDeleteRequest.getDeleteItems();

                int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItems);
                for (BatchDeleteRequest.DeleteItem deleteItem : deleteItems) {

                    boolean flag = false;
                    try {
                        if (deleteItem.getType() == FileTypeEnum.FILE) {
                            flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName());
                        } else if (deleteItem.getType() == FileTypeEnum.FOLDER) {
                            flag = fileService.deleteFolder(deleteItem.getPath(), deleteItem.getName());
                        }

                        if (flag) {
                            deleteSuccessCount++;
                        } else {
                            deleteFailCount++;
                        }
                    } catch (Exception e) {
                        LOGGER.error("删除文件/文件夹失败, 文件路径: {}, 文件名称: {}", deleteItem.getPath(), deleteItem.getName(), e);
                        deleteFailCount++;
                    }
                }

                if (totalCount > 1) {
                    //return ResponseResult.success("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个.");
                    LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个.");
                } else {
                    //return totalCount == deleteSuccessCount ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败");
                    LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个.");

                }
                //如果是1 说明成功删除
                if (deleteSuccessCount == 1) {
                    int valueDelete = deleteFiles(files.getId(), type);
                    if (valueDelete > 0) {
                        LocalSuccessCount++;
                        LOGGER.info("表结构成功删除");
                    } else {
                        System.out.println("没有记录被删除");
                    }
                } else {
                    LocalFailCount++;
                }
            }
        }
        return "Local批量删除 " + Localtotal + " 个, 删除成功 " + LocalSuccessCount + " 个, 失败 " + LocalFailCount + " 个.";
    }

    // 调用删除方法并返回删除数量
    public int deleteFolder(String folderId, String type) {
        return deleteFolderAndSubFiles(folderId, type);  // 返回删除的记录数量
    }

    // 递归删除文件夹及其子文件，并返回删除的记录数量
    public int deleteFolderAndSubFiles(String folderId, String type) {
        int deletedCount = 0;  // 记录删除的数量

        // 查询该文件夹下的所有子文件
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", folderId); // 根据父ID查询所有子文件
        List<TsFiles> subFiles = tsFilesMapper.selectList(queryWrapper);

        // 递归删除子文件
        for (TsFiles subFile : subFiles) {
            // 递归调用删除子文件的逻辑
            deletedCount += deleteFolderAndSubFiles(subFile.getId(), type);  // 累加删除数量

            //判断是删除本地还是minio
            if ("local".equals(type)) {
                //删除本地的时候判断minio路径是否为空  如果为空直接删除  如果不为空把workPath修改成空
                if (StringUtils.isNotEmpty(subFile.getBackupPath())) {
                    subFile.setWorkPath("");
                    tsFilesMapper.updateById(subFile);
                } else {
                    // 删除子文件记录（从数据库中删除）
                    tsFilesMapper.deleteById(subFile.getId());
                }

            } else {
                //删除minio的时候判断本地路径是否为空  如果为空直接删除  如果不为空把BackupPath修改成空
                if (StringUtils.isNotEmpty(subFile.getWorkPath())) {
                    subFile.setBackupPath("");
                    tsFilesMapper.updateById(subFile);
                } else {
                    // 删除子文件记录（从数据库中删除）
                    tsFilesMapper.deleteById(subFile.getId());
                }
            }
            deletedCount++;  // 每删除一个文件，数量加1
        }

        //删除修改自身
        TsFiles tsFilesData = tsFilesMapper.selectById(folderId);
        //判断是删除本地还是minio
        if ("local".equals(type)) {
            //删除本地的时候判断minio路径是否为空  如果为空直接删除  如果不为空把workPath修改成空
            if (StringUtils.isNotEmpty(tsFilesData.getBackupPath())) {
                tsFilesData.setWorkPath("");
                tsFilesMapper.updateById(tsFilesData);
            } else {
                // 删除子文件记录（从数据库中删除）
                tsFilesMapper.deleteById(tsFilesData.getId());
            }

        } else {
            //删除minio的时候判断本地路径是否为空  如果为空直接删除  如果不为空把BackupPath修改成空
            if (StringUtils.isNotEmpty(tsFilesData.getWorkPath())) {
                tsFilesData.setBackupPath("");
                tsFilesMapper.updateById(tsFilesData);
            } else {
                // 删除子文件记录（从数据库中删除）
                tsFilesMapper.deleteById(tsFilesData.getId());
            }
        }
        deletedCount++;  // 删除文件夹本身，数量加1

        return deletedCount;  // 返回删除的数量
    }


    public int deleteFiles(String id, String type) {
        int deletedCount = 0;
        TsFiles tsFilesData = tsFilesMapper.selectById(id);
        if (tsFilesData != null) {
            //判断是删除本地还是minio
            if ("local".equals(type)) {
                //删除本地的时候判断minio路径是否为空  如果为空直接删除  如果不为空把workPath修改成空
                if (StringUtils.isNotEmpty(tsFilesData.getBackupPath())) {
                    tsFilesData.setWorkPath("");
                    tsFilesMapper.updateById(tsFilesData);
                    deletedCount++;
                } else {
                    // 删除子文件记录（从数据库中删除）
                    tsFilesMapper.deleteById(tsFilesData.getId());
                    deletedCount++;

                }

            } else {
                //删除minio的时候判断本地路径是否为空  如果为空直接删除  如果不为空把BackupPath修改成空
                if (StringUtils.isNotEmpty(tsFilesData.getWorkPath())) {
                    tsFilesData.setBackupPath("");
                    tsFilesMapper.updateById(tsFilesData);
                    deletedCount++;

                } else {
                    // 删除子文件记录（从数据库中删除）
                    tsFilesMapper.deleteById(tsFilesData.getId());
                    deletedCount++;

                }
            }
        }
        return deletedCount;
    }


    /**********************************
     * 用途说明: 压缩文件夹接口
     * 参数说明  ids 文件id数组
     * 参数说明  compressedFormat 压缩文件格式
     * 参数说明  compressedName 压缩文件名称
     * 参数说明  compressedPath 压缩文件路径
     * 参数说明  covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在
     * 参数说明  parentId 父ID
     * 参数说明  path   根目录 /项目名/节点名称/
     * 返回值说明: com.yfd.platform.config.ResponseResult
     ***********************************/
    @Override
    public String compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId, String path) throws FileNotFoundException {
        //获取本地根路径
        StorageSourceConfig storageSourceConfig = getStorageConfig("filePath", "local");
        compressedPath = normalizePath(compressedPath);


        String returnResult = "";
        // 创建要压缩的文件夹列表
        List<Path> sourceDirs = new ArrayList<>(); // 修复：使用 ArrayList
        String[] splitIds = ids.split(",");
        // 数组转集合
        List<String> dataset = Arrays.asList(splitIds); // 使用 Arrays.asList 创建不可变列表
        List<TsFiles> filesList = tsFilesMapper.selectBatchIds(dataset);
        // 检查 filesList 是否为空
        if (filesList == null || filesList.isEmpty()) {
            throw new RuntimeException("未找到要压缩的文件或文件夹");
        }
        for (TsFiles tsFiles : filesList) {
            // 检查 tsFiles 的字段是否为空
            if (tsFiles.getWorkPath() == null || tsFiles.getFileName() == null) {
                throw new RuntimeException("文件或文件夹信息不完整");
            }

            // 构建完整路径
            String sourceDir = storageSourceConfig.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName();
            Path workpath = Paths.get(sourceDir);
            // 检查路径是否存在
            if (!Files.exists(workpath)) {
                throw new FileNotFoundException("路径不存在: " + path);
            }
            // 添加到压缩列表
            sourceDirs.add(workpath); // 修复：使用可变集合
        }
        try {
            String finalParentId = ensureFullPathExists(compressedPath, filesList.get(0).getNodeId(), filesList.get(0).getTaskId(), path);
            parentId = finalParentId;
            LOGGER.info("路径已全部存在，最终目录ID: {}", finalParentId);
        } catch (RuntimeException e) {
            LOGGER.error("路径创建失败: {}", e.getMessage());
            returnResult = "路径创建失败!";
            return returnResult;
        }


        // 规范化压缩包存放路径
        // 如果 compressedPath 是根目录，使用当前工作目录
        Path zipFilePath = Paths.get(storageSourceConfig.getValue() + path + compressedPath + compressedName + "." + compressedFormat);

        // 检查目标路径是否可写
        if (!Files.isWritable(zipFilePath.getParent())) {
            throw new RuntimeException("目标路径不可写: " + zipFilePath.getParent());
        }

        String zipFileName = compressedName + "." + compressedFormat;

        // 使用 try-with-resources 确保 zipOut 在使用后关闭
        // 设置压缩级别，可以选择调整
//            zipOut.setLevel(ZipArchiveOutputStream.STORED); // 或者使用其他的压缩级别
        // 调用压缩方法
        Boolean value = compressToSameDirectory(sourceDirs, compressedFormat, zipFilePath);
        if (value) {

            String workPath = normalizePath(path + compressedPath);
            //表结构增加    nodeId, String TaskId
            TsFiles tsFiles = new TsFiles();
            tsFiles.setTaskId(filesList.get(0).getTaskId());
            tsFiles.setNodeId(filesList.get(0).getNodeId());
            tsFiles.setWorkPath(workPath);
            tsFiles.setIsFile("FILE");

            // 获取文件大小（字节）
            long fileSizeInBytes = zipFilePath.toFile().length();
            // 转换为 MB 并保留两位小数
            double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0);
            String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数
            // 设置文件大小
            tsFiles.setFileSize(fileSizeFormatted);
            //tsFiles.setFileSize(String.valueOf(zipFilePath.toFile().length()));
            tsFiles.setParentId(parentId);
            tsFiles.setBackupPath("");
            tsFiles.setKeywords("");
            tsFiles.setDescription("");
            tsFiles.setFileName(zipFileName);

            //上传人是当前登录人
            UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
            LoginUser loginuser = (LoginUser) authentication.getPrincipal();
            // 设置当前时间
            LocalDateTime now = LocalDateTime.now();
            // 转换为 Timestamp
            Timestamp currentTime = Timestamp.valueOf(now);
            tsFiles.setUploadTime(currentTime);
            tsFiles.setUploader(loginuser.getUsername());
            // 通过 nodeid  taskid  名称查询  如果存在就不 新增

            QueryWrapper<TsFiles> queryWrapperTsFiles = new QueryWrapper<>();
            queryWrapperTsFiles.eq("node_id", filesList.get(0).getNodeId());
            queryWrapperTsFiles.eq("task_id", filesList.get(0).getTaskId());
            queryWrapperTsFiles.eq("work_path", workPath);
            queryWrapperTsFiles.eq("file_name", zipFileName);
            queryWrapperTsFiles.eq("parent_id", filesList.get(0).getParentId());
            TsFiles tsFilesdata = tsFilesMapper.selectOne(queryWrapperTsFiles);
            //covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在
            if ("0".equals(covered)) {
                //如果不为空 更新updateTime时间
                if (tsFilesdata != null) {
                    tsFilesdata.setUpdateTime(currentTime);
                    tsFilesMapper.updateById(tsFilesdata);
                } else {
                    //如果为空就新增
                    int valueAdded = tsFilesMapper.insert(tsFiles);
                    if (valueAdded == 1) {
                        returnResult = "压缩成功";
                    } else {
                        returnResult = "压缩失败";
                    }
                }

            } else {
                // 1提示文件存在   如果不为空返回提示
                if (tsFilesdata != null) {
                    returnResult = "文件或文件夹已存在！";
                } else {
                    //如果为空就新增
                    int valueAdded = tsFilesMapper.insert(tsFiles);
                    if (valueAdded == 1) {
                        returnResult = "压缩成功";
                    } else {
                        returnResult = "压缩失败";
                    }
                }
            }
            returnResult = "压缩成功";
        } else {
            returnResult = "压缩失败";
        }
        return returnResult;
    }


    // ================== 核心方法改造 ==================

    /**
     * 确保完整路径存在，不存在则逐层创建
     *
     * @param compressedPath 输入的完整路径（如 /38240bbb160450e03de33df911937d59/1/1/2/3/4/）
     * @param nodeId         当前节点ID（如 38240bbb160450e03de33df911937d59）
     * @return 最终目录的数据库ID（即最后一级目录的ID）
     */
    public String ensureFullPathExists(String compressedPath, String nodeId, String taskId, String currentPath) {
        // 设置当前时间
        LocalDateTime now = LocalDateTime.now();
        // 转换为 Timestamp
        Timestamp currentTime = Timestamp.valueOf(now);

        //上传人是当前登录人
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = (LoginUser) authentication.getPrincipal();


        // 1. 解析路径，提取有效层级目录名（跳过nodeId部分）
        List<String> pathSegments = Arrays.stream(compressedPath.split("/"))
                .filter(s -> !s.isEmpty())
                .collect(Collectors.toList());

//        // 校验路径是否以nodeId开头
//        if (pathSegments.isEmpty() || !pathSegments.get(0).equals(nodeId)) {
//            throw new RuntimeException("路径必须包含当前节点ID");
//        }
//        String nodePath = "/" + nodeId + "/";
//        if (compressedPath.equals(nodePath)) {
//            return "00";
//        }


        // 提取实际要处理的目录层级（去掉开头的nodeId）
//        List<String> dirSegments = null;
//        if (pathSegments.size() > 1) {
//            // 如果 pathSegments 大于 1，去掉 nodeId
//            dirSegments = pathSegments.subList(1, pathSegments.size());
//        }
//        if (dirSegments.isEmpty()) {
//            throw new RuntimeException("路径缺少有效目录层级");
//        }


        // 2. 初始化根目录信息（基于nodeId）
        String parentId = "00"; // 根目录的父ID为0

        // 3. 逐层处理目录（从第一个有效目录开始）
        for (String dirName : pathSegments) {
            //如果为空 查询文件表        // 检查当前层级目录是否存在
            TsFiles dir = tsFilesMapper.selectOne(new QueryWrapper<TsFiles>()
                    .eq("task_id", taskId)
                    .eq("node_id", nodeId)
                    .eq("parent_id", parentId)
                    .eq("work_path", currentPath)
                    .eq("file_name", dirName));

            if (dir == null) {
                // 创建本地文件夹（如 /38240bbb.../1/2/）

                //        //本地创建文件夹
                NewFolderRequest newFolderRequest = new NewFolderRequest();
                newFolderRequest.setStorageKey("local");
                newFolderRequest.setPath(currentPath);
                newFolderRequest.setPassword(null);
                newFolderRequest.setName(dirName);

                AbstractBaseFileService<?> fileServicefolder = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey());
                boolean flagfolder = fileServicefolder.newFolder(newFolderRequest.getPath(), newFolderRequest.getName());
                if (!flagfolder) {
                    throw new RuntimeException("目录创建失败: " + dirName);
                }

                // 插入数据库记录
                TsFiles tsFiles1 = new TsFiles();
                tsFiles1.setNodeId(nodeId);
                tsFiles1.setTaskId(taskId);
                tsFiles1.setIsFile("FOLDER");
                tsFiles1.setParentId(parentId);
                tsFiles1.setFileName(dirName);
                tsFiles1.setFileSize("0");
                tsFiles1.setWorkPath(currentPath);
                tsFiles1.setBackupPath("");
                tsFiles1.setKeywords("");
                tsFiles1.setDescription("");
                tsFiles1.setUploadTime(currentTime);
                tsFiles1.setUploader(loginuser.getUsername());
                //如果为空就新增
                int valueAdded = tsFilesMapper.insert(tsFiles1);
                if (valueAdded == 1) {
                    parentId = tsFiles1.getId();
                    currentPath = tsFiles1.getWorkPath() + tsFiles1.getFileName() + "/";
                }
            } else {
                parentId = dir.getId();
                currentPath = dir.getWorkPath() + dir.getFileName() + "/";
            }
        }

        return parentId;
    }


    private Boolean compressToSameDirectory(List<Path> sourcePaths, String compressedFormat, Path outputPath) {
        try {
            switch (compressedFormat.toUpperCase()) {
                case "ZIP":
                    return compressToZip(sourcePaths, outputPath);
                case "TAR":
                    return compressToTar(sourcePaths, outputPath);
                case "TAR.GZ":
                    return compressToTarGz(sourcePaths, outputPath);
                case "7Z":
                    return compressTo7z(sourcePaths, outputPath);
                default:
                    throw new IllegalArgumentException("不支持的压缩格式: " + compressedFormat);
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    // ================== ZIP格式压缩实现 ==================


    /**
     * 压缩文件/目录到指定路径
     *
     * @param sourcePaths 源文件/目录列表
     * @param outputPath  指定的输出路径（可以是目录或完整文件路径）
     */
    public boolean compressToZip(List<Path> sourcePaths, Path outputPath) {
        if (sourcePaths.isEmpty()) {
            LOGGER.warn("源路径列表为空，无法压缩");
            return false;
        }

        // 生成最终 ZIP 路径
        Path finalZipPath = generateFinalZipPath(sourcePaths.get(0), outputPath);
        addedEntries.clear();

        try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(finalZipPath))) {
            zipOut.setLevel(Deflater.DEFAULT_COMPRESSION);

            for (Path sourcePath : sourcePaths) {
                if (Files.isDirectory(sourcePath)) {
                    String baseDir = sourcePath.getFileName().toString();
                    addDirectoryToZip(sourcePath, zipOut, baseDir);
                } else {
                    addFileToZip(sourcePath, zipOut, "");
                }
            }

            zipOut.finish();
            return validateZip(finalZipPath);
        } catch (Exception e) {
            LOGGER.error("压缩失败", e);
            deleteQuietly(finalZipPath);
            return false;
        }
    }


    /**
     * 生成最终 ZIP 路径
     */
    private Path generateFinalZipPath(Path sourcePath, Path outputPath) {
        // 如果输出路径是目录，则在目录中生成默认名称的 ZIP
        if (Files.isDirectory(outputPath)) {
            String baseName = sourcePath.getFileName().toString().replaceFirst("[.][^.]+$", "");
            return outputPath.resolve(baseName + ".zip");
        }
        // 否则直接使用指定路径
        return outputPath;
    }


    private void addDirectoryToZip(Path dir, ZipOutputStream zipOut, String baseDir) throws IOException {
        final Path sourceDir = dir;

        Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path currentDir, BasicFileAttributes attrs) throws IOException {
                // 跳过源目录自身（由外部显式添加）
                if (currentDir.equals(sourceDir)) {
                    return FileVisitResult.CONTINUE;
                }

                // 生成相对路径的条目名称
                Path relativePath = sourceDir.relativize(currentDir);
                String entryName = baseDir + "/" + relativePath.toString().replace("\\", "/") + "/";

                // 确保条目唯一性
                addUniqueDirectoryEntry(entryName, zipOut);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Path relativePath = sourceDir.relativize(file);
                String entryName = baseDir + "/" + relativePath.toString().replace("\\", "/");
                addFileEntry(file, entryName, zipOut);
                return FileVisitResult.CONTINUE;
            }
        });

        // 显式添加根目录条目（确保至少存在一个目录条目）
        String rootEntry = baseDir + "/";
        addUniqueDirectoryEntry(rootEntry, zipOut);
    }

    private void addUniqueDirectoryEntry(String entryName, ZipOutputStream zipOut) throws IOException {
        if (!addedEntries.contains(entryName)) {
            ZipEntry entry = new ZipEntry(entryName);
            zipOut.putNextEntry(entry);
            zipOut.closeEntry();
            addedEntries.add(entryName);
        }
    }

    private void addFileToZip(Path file, ZipOutputStream zipOut, String baseDir) throws IOException {
        String entryName = baseDir + file.getFileName().toString();
        addFileEntry(file, entryName, zipOut);
    }

    private void addFileEntry(Path file, String entryName, ZipOutputStream zipOut) throws IOException {
        if (addedEntries.contains(entryName)) {
            LOGGER.warn("跳过重复条目: {}", entryName);
            return;
        }

        LOGGER.debug("添加文件条目: {} (大小: {} 字节)", entryName, Files.size(file));
        ZipEntry entry = new ZipEntry(entryName);
        entry.setSize(Files.size(file));
        entry.setTime(Files.getLastModifiedTime(file).toMillis());
        zipOut.putNextEntry(entry);

        try (InputStream input = Files.newInputStream(file)) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = input.read(buffer)) != -1) {
                zipOut.write(buffer, 0, bytesRead);
            }
        }

        zipOut.closeEntry();
        addedEntries.add(entryName);
    }


    private boolean validateZip(Path zipPath) {
        try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
            Enumeration<? extends ZipEntry> entries = zipFile.entries(); // 获取所有条目
            if (!entries.hasMoreElements()) {
                LOGGER.warn("ZIP 文件为空: {}", zipPath);
                return false;
            }

            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                try (InputStream is = zipFile.getInputStream(entry)) {
                    byte[] buffer = new byte[1024];
                    while (is.read(buffer) != -1) {
                    } // 读取条目内容
                }
            }

            LOGGER.info("ZIP 文件验证成功: {}", zipPath);
            return true;
        } catch (Exception e) {
            LOGGER.error("ZIP 文件验证失败: {}", zipPath, e);
            return false;
        }
    }

    private void deleteQuietly(Path path) {
        try {
            Files.deleteIfExists(path);
        } catch (IOException ignored) {
        }
    }

    /**
     * 生成压缩文件路径（去除源文件扩展名）
     */
    private Path getCompressedFilePath(Path sourcePath, String extension) {
        String fileName = sourcePath.getFileName().toString();
        String baseName = fileName.contains(".")
                ? fileName.substring(0, fileName.lastIndexOf('.'))
                : fileName;
        return sourcePath.resolveSibling(baseName + "." + extension);
    }


// ================== TAR格式压缩实现 ==================

    /**
     * 压缩为纯TAR格式（不进行GZ压缩）
     *
     * @param sourcePaths 要压缩的源路径列表（文件或目录）
     * @param outputPath  输出文件路径（必须以.tar结尾）
     * @return 是否压缩成功
     * @throws IOException 文件操作异常
     */
    private boolean compressToTar(List<Path> sourcePaths, Path outputPath) throws IOException {
        try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(Files.newOutputStream(outputPath))) {
            tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);

            for (Path sourcePath : sourcePaths) {
                // 关键修改：始终使用源路径的父目录作为basePath
                Path basePath = sourcePath.getParent();

                Files.walkFileTree(sourcePath, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        String entryName = basePath.relativize(file).toString().replace(File.separator, "/");
                        addFileToTar(file, entryName, tarOut);
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                        if (!dir.equals(sourcePath)) { // 不跳过源目录本身
                            String dirName = basePath.relativize(dir).toString().replace(File.separator, "/") + "/";
                            addDirToTar(dir, dirName, tarOut);
                        }
                        return FileVisitResult.CONTINUE;
                    }
                });
            }
            tarOut.finish();
            return true;
        }
    }

    // 辅助方法：添加文件条目
    private void addFileToTar(Path file, String entryName, TarArchiveOutputStream tarOut) throws IOException {
        TarArchiveEntry entry = new TarArchiveEntry(file.toFile(), entryName);
        entry.setSize(Files.size(file));
        tarOut.putArchiveEntry(entry);
        try (InputStream is = Files.newInputStream(file)) {
            IOUtils.copy(is, tarOut);
        }
        tarOut.closeArchiveEntry();
    }

    // 辅助方法：添加目录条目
    private void addDirToTar(Path dir, String dirName, TarArchiveOutputStream tarOut) throws IOException {
        TarArchiveEntry entry = new TarArchiveEntry(dir.toFile(), dirName);
        tarOut.putArchiveEntry(entry);
        tarOut.closeArchiveEntry();
    }


    // ================== TAR.GZ格式压缩实现 ==================
    private boolean compressToTarGz(List<Path> sourcePaths, Path outputPath) throws IOException {
        try (OutputStream fos = Files.newOutputStream(outputPath);
             GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos);
             TarArchiveOutputStream tarOut = new TarArchiveOutputStream(gzos)) {

            // 设置支持长文件名（超过100字符）
            tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);

            // 遍历所有源路径并添加到压缩文件
            for (Path path : sourcePaths) {
                addToTarArchive(path, tarOut, "");
            }

            tarOut.finish();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递归添加文件到TAR压缩流
     *
     * @param source    当前要添加的路径
     * @param tarOut    TAR输出流
     * @param parentDir 在压缩文件中的父目录路径（用于保持目录结构）
     */
    private void addToTarArchive(Path source, TarArchiveOutputStream tarOut, String parentDir) throws IOException {
        // 获取文件在压缩包中的入口名称（替换路径分隔符为/）
        String entryName = parentDir + source.getFileName().toString().replace(File.separator, "/");

        // 处理目录
        if (Files.isDirectory(source)) {
            entryName += "/";  // 目录需要以/结尾
            TarArchiveEntry dirEntry = new TarArchiveEntry(source.toFile(), entryName);
            tarOut.putArchiveEntry(dirEntry);
            tarOut.closeArchiveEntry();

            // 递归处理子目录
            try (DirectoryStream<Path> children = Files.newDirectoryStream(source)) {
                for (Path child : children) {
                    addToTarArchive(child, tarOut, entryName);
                }
            }
        }
        // 处理文件
        else {
            TarArchiveEntry fileEntry = new TarArchiveEntry(source.toFile(), entryName);
            fileEntry.setSize(Files.size(source));
            tarOut.putArchiveEntry(fileEntry);
            try (InputStream input = Files.newInputStream(source)) {
                IOUtils.copy(input, tarOut);
            }
            tarOut.closeArchiveEntry();
        }
    }

    /**
     * 压缩为7z格式
     *
     * @param sourcePaths 要压缩的源路径列表
     * @param outputPath  输出文件路径（必须包含文件名）
     */
    private boolean compressTo7z(List<Path> sourcePaths, Path outputPath) throws IOException {
        try (SevenZOutputFile sevenZOutput = new SevenZOutputFile(outputPath.toFile())) {

            // 遍历所有源路径并添加到压缩文件
            for (Path path : sourcePaths) {
                addTo7zArchive(path, sevenZOutput, "");
            }

            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递归添加文件到7z压缩流
     *
     * @param source       当前要添加的路径
     * @param sevenZOutput 7z输出流
     * @param parentDir    在压缩文件中的父目录路径（用于保持目录结构）
     */
    private void addTo7zArchive(Path source, SevenZOutputFile sevenZOutput, String parentDir) throws IOException {
        // 生成压缩包内条目名称
        String entryName = parentDir.isEmpty()
                ? source.getFileName().toString().replace(File.separator, "/")
                : parentDir + "/" + source.getFileName().toString().replace(File.separator, "/");

        // 创建压缩条目并设置属性
        SevenZArchiveEntry entry = new SevenZArchiveEntry();
        entry.setName(entryName);
        entry.setDirectory(Files.isDirectory(source));
        sevenZOutput.putArchiveEntry(entry);

        // 如果是文件则写入内容
        if (!entry.isDirectory()) {
            try (InputStream input = Files.newInputStream(source)) {
                byte[] buffer = new byte[8192];
                int len;
                while ((len = input.read(buffer)) > 0) {
                    sevenZOutput.write(buffer, 0, len);
                }
            }
        }
        sevenZOutput.closeArchiveEntry();

        // 递归处理子目录
        if (entry.isDirectory()) {
            try (DirectoryStream<Path> children = Files.newDirectoryStream(source)) {
                for (Path child : children) {
                    addTo7zArchive(child, sevenZOutput, entryName);
                }
            }
        }
    }

    // ================== 通用工具方法 ==================
    private interface EntryProcessor {
        void process(String entryName, boolean isDir) throws IOException;
    }

    /*************************************解压缩*******************************************/

    /**********************************
     * 用途说明: 解压缩接口
     * 参数说明  id  要解压的文件id
     * 参数说明  decompressionPath 解压缩路径
     * 参数说明  parentId 父ID
     * 参数说明  path   根目录 /项目名/节点名称/
     * 返回值说明: com.yfd.platform.config.ResponseResult
     ***********************************/
    @Override
    public String decompressionFolder(String id, String decompressionPath, String parentId, String path) {
        // 查询本地文件路径根目录（如 E:\yun）
        StorageSourceConfig storageSourceConfig = getStorageConfig("filePath", "local");
        decompressionPath = normalizePath(decompressionPath);
        // 1. 获取压缩包记录
        TsFiles zipFileRecord = tsFilesMapper.selectById(id);
        try {
            String finalParentId = ensureFullPathExists(decompressionPath, zipFileRecord.getNodeId(), zipFileRecord.getTaskId(), path);
            parentId = finalParentId;
            LOGGER.info("路径已全部存在，最终目录ID: {}", finalParentId);
        } catch (RuntimeException e) {
            LOGGER.error("路径创建失败: {}", e.getMessage());
            return "路径创建失败!";
        }
        //解压以后的路径
        String extractPath = normalizePath(path + decompressionPath);

        String zipName = getFileNameWithoutExtension(zipFileRecord.getFileName()); // 示例：222
        try {
            // 2. 判断压缩包类型（单文件还是文件夹）
            Path zipFilePath = Paths.get(storageSourceConfig.getValue(), zipFileRecord.getWorkPath(), zipFileRecord.getFileName());
            // 3. 判断压缩包内容类型
            boolean hasFolder = hasFolder(zipFilePath);
//            // 4. 构建解压目标路径
            Path destRoot;
            if (hasFolder) {
                // 如果有文件夹，创建子目录（如 E:/yun/333/222/）
                destRoot = Paths.get(storageSourceConfig.getValue(), extractPath, zipName);
            } else {
                // 如果只有文件，直接解压到目标目录（如 E:/yun/333/）
                destRoot = Paths.get(storageSourceConfig.getValue(), extractPath);
            }
            Files.createDirectories(destRoot);

            // 5.执行解压（到子目录）
            File unzippedRoot = unzipToTemp(
                    Paths.get(storageSourceConfig.getValue(), zipFileRecord.getWorkPath(), zipFileRecord.getFileName()),
                    destRoot.toString()
            );

            // 6. 处理解压内容
            if (hasFolder) {
                // 如果有文件夹，创建文件夹记录并处理子内容
                TsFiles rootFolder = createFolderRecord(
                        zipName, // 文件夹名称222
                        parentId, // 父ID是333的ID
                        buildFolderPath(extractPath), // 修正为 /333/222/
                        zipFileRecord.getTaskId(),
                        zipFileRecord.getNodeId(),
                        zipFileRecord.getUploader()
                );
                String rootFolderId = rootFolder.getId();

                // 7. 处理子内容（路径从/333/222/开始）  关键点 所有的文件夹都是上级路径 文件是上级路径加名称
                processFolderContents(
                        unzippedRoot,
                        rootFolderId,
                        buildFolderPath(extractPath + "/" + zipName + "/"), // 修正为 /333/222/
                        zipFileRecord.getTaskId(),
                        zipFileRecord.getNodeId(),
                        zipFileRecord.getUploader()
                );
            } else {
                // 如果只有文件，直接创建文件记录
                File[] files = unzippedRoot.listFiles();
                if (files != null && files.length > 0) {
                    for (File file : files) {
                        if (file.isDirectory()) {
                            continue;
                        } else {
                            // 获取文件大小（字节）
                            long fileSizeInBytes = file.length();
                            // 转换为 MB 并保留两位小数
                            double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0);
                            String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数

                            TsFiles fileRecord = createFileRecord(
                                    file.getName(),
                                    parentId,
                                    buildFolderPath(decompressionPath),
                                    zipFileRecord.getTaskId(),
                                    zipFileRecord.getNodeId(),
                                    zipFileRecord.getUploader(),
                                    fileSizeFormatted
                            );
                        }
                    }
                }

            }
            return "解压存储成功";
        } catch (Exception e) {
            LOGGER.error("解压失败: " + e.getMessage());
            throw new RuntimeException("操作失败: " + e.getMessage());
        }
    }


    /**
     * 创建文件记录
     */
    private TsFiles createFileRecord(String fileName, String parentId, String workPath,
                                     String taskId, String nodeId, String uploader, String fileSize) {

        QueryWrapper<TsFiles> query = new QueryWrapper<TsFiles>()
                .eq("node_id", nodeId)
                .eq("task_id", taskId)
                .eq("work_path", workPath)
                .eq("file_name", fileName)
                .eq("is_file", "FILE")
                .eq("parent_id", parentId);
        TsFiles existing = tsFilesMapper.selectOne(query);
        if (existing != null) {
            return existing;
        } else {
            TsFiles fileRecord = new TsFiles();
            fileRecord.setNodeId(nodeId);
            fileRecord.setTaskId(taskId);
            fileRecord.setParentId(parentId);
            fileRecord.setFileName(fileName);
            fileRecord.setWorkPath(workPath);
            fileRecord.setIsFile("FILE");
            fileRecord.setFileSize(fileSize);
            fileRecord.setUploadTime(new Timestamp(System.currentTimeMillis()));
            operateTsfeiles(fileRecord); // 插入或更新记录
            return fileRecord;
        }
    }

    /**
     * 创建文件夹记录
     */
    private TsFiles createFolderRecord(String folderName, String parentId, String workPath,
                                       String taskId, String nodeId, String uploader) {
        //上传人是当前登录人
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = (LoginUser) authentication.getPrincipal();
        TsFiles folderRecord = new TsFiles();
        folderRecord.setNodeId(nodeId);
        folderRecord.setTaskId(taskId);
        folderRecord.setParentId(parentId);
        folderRecord.setFileName(folderName);
        folderRecord.setWorkPath(workPath);
        folderRecord.setIsFile("FOLDER");
        folderRecord.setFileSize("0");
        folderRecord.setUploadTime(new Timestamp(System.currentTimeMillis()));
        operateTsfeiles(folderRecord); // 插入或更新记录
        return folderRecord;
    }

    /**
     * 递归处理子内容（关键：区分根目录和子内容路径）
     */
    private void processFolderContents(File currentDir, String parentId, String parentWorkPath,
                                       String taskId, String nodeId, String uploader) {
        File[] children = currentDir.listFiles();
        if (children == null) return;

        for (File child : children) {
            String childName = child.getName();
            String childWorkPath = parentWorkPath + childName + (child.isDirectory() ? "/" : ""); // 修正为 /333/222/childName/
            if (child.isDirectory()) {
                // 处理子目录（示例：555）
                String folderWorkPath = parentWorkPath;

                TsFiles folderRecord = new TsFiles();
                folderRecord.setNodeId(nodeId);
                folderRecord.setTaskId(taskId);
                folderRecord.setParentId(parentId); // 父ID是222的ID
                folderRecord.setFileName(child.getName());
                folderRecord.setWorkPath(folderWorkPath); // /333/222/555/
                folderRecord.setIsFile("FOLDER");
                folderRecord.setFileSize("0");
                folderRecord.setUploadTime(new Timestamp(System.currentTimeMillis()));
                operateTsfeiles(folderRecord);

                // 递归处理子目录内容
                processFolderContents(
                        child,
                        folderRecord.getId(),
                        childWorkPath,
                        taskId,
                        nodeId,
                        uploader
                );
            } else {

                // 获取文件大小（字节）
                long fileSizeInBytes = child.length();
                // 转换为 MB 并保留两位小数
                double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0);
                String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数
                // 处理文件（示例：1.txt）
                TsFiles fileRecord = new TsFiles();
                fileRecord.setNodeId(nodeId);
                fileRecord.setTaskId(taskId);
                fileRecord.setParentId(parentId); // 父ID是222的ID
                fileRecord.setFileName(child.getName());
                fileRecord.setWorkPath(parentWorkPath); // /333/222/
                fileRecord.setIsFile("FILE");
                fileRecord.setUploadTime(new Timestamp(System.currentTimeMillis()));
                fileRecord.setFileSize(fileSizeFormatted);
                operateTsfeiles(fileRecord);
//
//                tsFilesMapper.insert(fileRecord);
            }
        }
    }

    private void operateTsfeiles(TsFiles tsFiles) {
        //上传人是当前登录人
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = (LoginUser) authentication.getPrincipal();

        String path = normalizePath(tsFiles.getWorkPath());
        tsFiles.setWorkPath(path);
        QueryWrapper<TsFiles> query = new QueryWrapper<TsFiles>()
                .eq("node_id", tsFiles.getNodeId())
                .eq("task_id", tsFiles.getTaskId())
                .eq("work_path", tsFiles.getWorkPath())
                .eq("file_name", tsFiles.getFileName())
                .eq("is_file", tsFiles.getIsFile());
        TsFiles existing = tsFilesMapper.selectOne(query);
        if (existing != null) {
            existing.setUpdateTime(new Timestamp(System.currentTimeMillis()));
            existing.setUploader(loginuser.getUsername());
            tsFilesMapper.updateById(existing);
        } else {
            tsFiles.setUploader(loginuser.getUsername());
            tsFilesMapper.insert(tsFiles);
        }
    }

    // 文件夹路径标准化（确保以/结尾）
    private String buildFolderPath(String path) {
        return path.endsWith("/") ? path : path + "/";
    }


    // 新增：统一解压入口方法（保持原有逻辑结构）
    private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException {
        String fileName = zipFilePath.getFileName().toString();
        String ext = getFileExtension(fileName).toLowerCase();

        // 根据文件类型调用不同解压实现
        if (ext.equals(".zip")) {
            return unzipFile(zipFilePath, baseDir);
        } else if (fileName.endsWith(".tar.gz")) {
            return unTarGzFile(zipFilePath, baseDir);
        } else if (ext.equals(".tar")) {
            return unTarFile(zipFilePath, baseDir);
        } else if (ext.equals(".7z")) {
            return un7zFile(zipFilePath, baseDir);
        } else {
            throw new IllegalArgumentException("不支持的压缩格式: " + ext);
        }
    }

    // 修改后的判断是否包含文件夹方法（支持多格式）
    private boolean hasFolder(Path zipFilePath) throws IOException {
        String fileName = zipFilePath.getFileName().toString();
        String ext = getFileExtension(fileName).toLowerCase();

        if (ext.equals(".zip")) {
            return hasFolderInZip(zipFilePath);
        } else if (fileName.endsWith(".tar.gz")) {
            return hasFolderInTarGz(zipFilePath);
        } else if (ext.equals(".tar")) {
            return hasFolderInTar(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;
        }
    }

    // 新增：TAR格式文件夹判断
    private boolean hasFolderInTar(Path filePath) throws IOException {
        // 参数校验
        if (!filePath.toString().toLowerCase().endsWith(".tar")) {
            throw new IllegalArgumentException("输入文件必须以.tar结尾");
        }
        if (!Files.exists(filePath)) {
            throw new FileNotFoundException("TAR文件不存在: " + filePath);
        }

        try (InputStream fis = Files.newInputStream(filePath);
             TarArchiveInputStream tis = new TarArchiveInputStream(fis)) {

            TarArchiveEntry entry;
            while ((entry = tis.getNextTarEntry()) != null) {
                // 忽略MAC系统元数据目录
                if (entry.getName().startsWith("__MACOSX/")) {
                    continue;
                }

                // 发现目录立即返回true
                if (entry.isDirectory()) {
                    // 额外验证：确保不是空名称或根目录标记
                    if (!entry.getName().trim().isEmpty() && !entry.getName().equals("./")) {
                        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).normalize();
        java.nio.file.Files.createDirectories(destRoot);

        // 尝试UTF-8和GBK编码
        Charset[] charsets = {StandardCharsets.UTF_8, Charset.forName("GBK")};
        for (Charset charset : charsets) {
            try (ZipInputStream zis = new ZipInputStream(java.nio.file.Files.newInputStream(sourcePath), charset)) {
                ZipEntry entry;
                while ((entry = zis.getNextEntry()) != null) {
                    processZipEntry(zis, entry, destRoot);
                }
                return destRoot.toFile(); // 解压成功直接返回
            } catch (IllegalArgumentException | IOException e) {
                LOGGER.debug("编码 {} 解压失败，尝试下一个编码", charset, e);
            }
        }
        throw new IOException("无法使用UTF-8或GBK编码解压文件");
    }

    private void processZipEntry(ZipInputStream zis, ZipEntry entry, Path destRoot) throws IOException {
        // 1. 跳过所有系统文件和隐藏文件
        if (shouldSkipEntry(entry)) {
            LOGGER.debug("跳过系统文件：{}", entry.getName());
            return;
        }

        // 2. 消毒文件名并构建安全路径
        String sanitizedName = sanitizeFileName(entry.getName());
        Path targetPath = buildSafePath(destRoot, sanitizedName);

        // 3. 仅处理实际文件（跳过空目录）
        if (!entry.isDirectory()) {
            writeFileContent(zis, targetPath);
        }
    }

    /**
     * 判断是否需要跳过条目
     */
    private boolean shouldSkipEntry(ZipEntry entry) {
        String name = entry.getName();
        return name.startsWith("__MACOSX/") ||  // macOS系统文件
                name.contains("/.DS_Store") ||  // macOS资源文件
                name.startsWith(".") ||          // 隐藏文件
                name.endsWith("Thumbs.db");      // Windows缩略图文件
    }

    /**
     * 文件名消毒逻辑（保留路径分隔符 /）
     */
    private String sanitizeFileName(String original) {
        // 替换非法字符（保留 / 和 \）
        String sanitized = original
                .replaceAll("[*?\"<>|\0]", "_")  // 仅替换 * ? " < > | 和空字符
                .replace("\\", "/")              // 统一路径分隔符为 /
                .replaceFirst("^/+", "");        // 去除开头的斜杠

        // 防御路径穿越攻击
        sanitized = sanitized.replaceAll("\\.\\./", "_");
        return sanitized;
    }

    /**
     * 构建安全路径
     */
    private Path buildSafePath(Path destRoot, String sanitizedName) throws IOException {
        Path targetPath = destRoot.resolve(sanitizedName).normalize();

        // 二次路径校验
        if (!targetPath.startsWith(destRoot)) {
            throw new IOException("检测到非法路径穿越：" + targetPath);
        }
        return targetPath;
    }

    /**
     * 写入文件内容（仅在需要时创建目录）
     */
    private void writeFileContent(ZipInputStream zis, Path targetPath) throws IOException {
        // 仅当文件不存在时才写入
        if (!java.nio.file.Files.exists(targetPath)) {
            // 按需创建父目录
            Path parent = targetPath.getParent();
            if (parent != null && !java.nio.file.Files.exists(parent)) {
                java.nio.file.Files.createDirectories(parent);
            }

            // 使用缓冲流提升性能
            try (BufferedOutputStream os = new BufferedOutputStream(java.nio.file.Files.newOutputStream(targetPath))) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = zis.read(buffer)) != -1) {
                    os.write(buffer, 0, bytesRead);
                }
            }
            LOGGER.debug("成功解压文件：{}", targetPath);
        } else {
            LOGGER.warn("文件已存在，跳过覆盖：{}", targetPath);
        }
    }

    /**
     * 路径安全验证（增强版）
     */
    private void validatePathSafetya(Path targetPath, Path destRoot) throws IOException {
        Path normalizedDest = destRoot.toAbsolutePath().normalize();
        Path normalizedTarget = targetPath.toAbsolutePath().normalize();

        if (!normalizedTarget.startsWith(normalizedDest)) {
            throw new IOException("路径安全验证失败：" + normalizedTarget
                    + " 超出根目录 " + normalizedDest);
        }
    }


    /**
     * 解压TAR.GZ格式文件（含GZIP压缩的TAR包）
     *
     * @param sourcePath 压缩文件路径（必须是以.tar.gz结尾的文件）
     * @param baseDir    解压目标目录路径
     * @return 解压后的根目录File对象
     * @throws IOException 文件操作异常或安全校验失败
     */
    private File unTarGzFile(Path sourcePath, String baseDir) throws IOException {
        // 参数校验
        if (!sourcePath.toString().toLowerCase().endsWith(".tar.gz")) {
            throw new IllegalArgumentException("文件必须是 .tar.gz 格式");
        }

        Path destRoot = Paths.get(baseDir).toAbsolutePath();
        Files.createDirectories(destRoot);

        try (TarArchiveInputStream tis = new TarArchiveInputStream(
                new GzipCompressorInputStream(Files.newInputStream(sourcePath)))) {

            TarArchiveEntry entry;
            while ((entry = tis.getNextTarEntry()) != null) {
                // 跳过无效条目
                if (entry.getName() == null || entry.getName().contains("__MACOSX")) {
                    continue;
                }

                // 路径规范化
                String normalizedPath = normalizePathtargz(entry.getName(), sourcePath.getFileName().toString());
                if (normalizedPath.isEmpty()) {
                    continue;
                }

                Path targetPath = destRoot.resolve(normalizedPath).normalize();
                validatePath(targetPath, destRoot);

                // 处理目录/文件
                if (entry.isDirectory()) {
                    Files.createDirectories(targetPath);
                } else {
                    Files.createDirectories(targetPath.getParent());
                    try (OutputStream os = Files.newOutputStream(targetPath)) {
                        IOUtils.copy(tis, os);
                    }
                }

                // 保留Unix权限（仅在非Windows系统生效）
                if (!System.getProperty("os.name").toLowerCase().contains("win")) {
                    try {
                        Files.setPosixFilePermissions(
                                targetPath,
                                PosixFilePermissions.fromString(getPosixMode(entry.getMode()))
                        );
                    } catch (IllegalArgumentException e) {
                        // 捕获非法权限格式异常，避免进程终止
                        System.err.println("警告: 无法设置权限 [" + entry.getMode() + "] 文件: " + targetPath);
                    }
                }
            }
        }
        return destRoot.toFile();
    }

    /**
     * 将TAR文件条目的权限位转换为POSIX格式字符串
     * (修正1：移除空格，修正2：屏蔽高位)
     */
    private String getPosixMode(int mode) {
        // 仅保留低9位权限信息（用户/组/其他）
        mode &= 0777;
        return String.format("%s%s%s%s%s%s%s%s%s",
                (mode & 0400) != 0 ? "r" : "-",  // 用户读
                (mode & 0200) != 0 ? "w" : "-",  // 用户写
                (mode & 0100) != 0 ? "x" : "-",  // 用户执行
                (mode & 0040) != 0 ? "r" : "-",  // 组读
                (mode & 0020) != 0 ? "w" : "-",  // 组写
                (mode & 0010) != 0 ? "x" : "-",  // 组执行
                (mode & 0004) != 0 ? "r" : "-",  // 其他读
                (mode & 0002) != 0 ? "w" : "-",  // 其他写
                (mode & 0001) != 0 ? "x" : "-"); // 其他执行
    }


    /**
     * 验证目标路径安全性（防止路径穿越攻击）
     */
    private void validatePath(Path targetPath, Path destRoot) throws IOException {
        if (!targetPath.normalize().startsWith(destRoot.normalize())) {
            throw new IOException("危险路径: " + targetPath);
        }
    }

    /**
     * 解压TAR文件（自动处理嵌套目录问题）
     *
     * @param sourcePath 源TAR文件路径
     * @param baseDir    目标目录（解压到此目录下）
     * @return 解压后的根目录
     */
    private File unTarFile(Path sourcePath, String baseDir) throws IOException {
        Path destRoot = Paths.get(baseDir).toAbsolutePath();
        Files.createDirectories(destRoot);

        try (TarArchiveInputStream tis = new TarArchiveInputStream(Files.newInputStream(sourcePath))) {
            TarArchiveEntry entry;
            while ((entry = tis.getNextTarEntry()) != null) {
                // 处理路径：移除可能重复的顶层目录名
                String normalizedName = normalizeEntryPath(entry.getName(), sourcePath.getFileName().toString());

                Path targetPath = destRoot.resolve(normalizedName).normalize();
                validatePathSafety(targetPath, destRoot);

                if (entry.isDirectory()) {
                    Files.createDirectories(targetPath);
                } else {
                    Files.createDirectories(targetPath.getParent());
                    try (OutputStream os = Files.newOutputStream(targetPath)) {
                        IOUtils.copy(tis, os);
                    }
                }
            }
        }
        return destRoot.toFile();
    }

    /**
     * 智能规范化压缩包内路径
     *
     * @param entryPath       压缩包内原始路径
     * @param archiveFilename 压缩包文件名（如"111.tar.gz"）
     * @return 处理后的路径
     */
    private String normalizeEntryPath(String entryPath, String archiveFilename) {
        // 基础处理：移除开头的./和多余的/
        String path = entryPath.replaceAll("^\\./", "").replaceAll("/+", "/");

        // 获取预期的根目录名（去掉压缩扩展名）
        String baseName = archiveFilename.replaceFirst("\\.(tar|gz|7z|tar\\.gz)$", "");

        // 情况1：路径直接以baseName开头（如"111/22/33.txt"）
        if (path.startsWith(baseName + "/")) {
            return path.substring(baseName.length() + 1);
        }

        // 情况2：路径已经是扁平结构（如"22/33.txt"）
        return path;
    }

    /**
     * 安全规范化压缩包内路径
     *
     * @param entryPath   原始路径（如 "111/222/3.txt"）
     * @param archiveName 压缩包文件名（如 "111.7z"）
     * @return 规范化后的路径（如 "222/3.txt"）
     */
    private String normalizePathtargz(String entryPath, String archiveName) {
        if (entryPath == null || entryPath.trim().isEmpty()) {
            return "";
        }

        // 统一路径分隔符并清理特殊字符
        String path = entryPath.replace("\\", "/")
                .replaceAll("^\\./", "")
                .replaceAll("/+", "/")
                .trim();

        // 移除压缩包文件名对应的顶层目录（如果存在）
        String baseName = archiveName.replaceAll("\\.(tar\\.gz|gz|tar|7z)$", "");
        if (!baseName.isEmpty() && path.startsWith(baseName + "/")) {
            return path.substring(baseName.length() + 1);
        }
        return path;
    }

    /**
     * 解压7Z格式文件
     *
     * @param sourcePath 压缩文件路径（必须是以.7z结尾的文件）
     * @param baseDir    解压目标目录路径
     * @return 解压后的根目录File对象
     */
    private File un7zFile(Path sourcePath, String baseDir) throws IOException {
        // 参数校验
        if (!sourcePath.getFileName().toString().toLowerCase().endsWith(".7z")) {
            throw new IllegalArgumentException("必须是.7z文件");
        }

        Path destRoot = Paths.get(baseDir).toAbsolutePath();
        Files.createDirectories(destRoot);

        try (SevenZFile szFile = new SevenZFile(sourcePath.toFile())) {
            SevenZArchiveEntry entry;
            while ((entry = szFile.getNextEntry()) != null) {
                // 跳过无效条目
                if (entry.getName() == null || entry.getName().contains("__MACOSX")) {
                    continue;
                }

                // 关键修复：使用智能路径规范化
                String normalizedPath = smartNormalizePath7z(entry.getName(), sourcePath.getFileName().toString());
                if (normalizedPath.isEmpty()) {
                    continue; // 跳过根目录条目
                }

                Path targetPath = destRoot.resolve(normalizedPath).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();
    }

    /**
     * 智能路径规范化（修复7z/tar.gz多层级问题）
     *
     * @param entryPath   压缩包内原始路径（如 "111/222/3.txt"）
     * @param archiveName 压缩包文件名（如 "111.7z"）
     * @return 规范化后的路径（如 "222/3.txt"）
     */
    private String smartNormalizePath7z(String entryPath, String archiveName) {
        if (entryPath == null || entryPath.trim().isEmpty()) {
            return "";
        }

        // 统一路径分隔符
        String path = entryPath.replace("\\", "/")
                .replaceAll("/+", "/")
                .trim();

        // 移除压缩包名称对应的冗余顶层目录
        String baseName = archiveName.replaceAll("\\.(7z|tar\\.gz|tar|zip)$", "");
        if (!baseName.isEmpty()) {
            // 情况1：路径直接以baseName开头（如 "111/222/3.txt"）
            if (path.startsWith(baseName + "/")) {
                return path.substring(baseName.length() + 1);
            }
            // 情况2：路径是baseName自身（如 "111/"）
            if (path.equals(baseName) || path.equals(baseName + "/")) {
                return "";
            }
        }

        return path;
    }

    /**
     * 转换Unix权限位为rwx格式
     *
     * @param mode 权限位（如0755）
     */
    private String getPosixPermissions(int mode) {
        return String.format("%s%s%s",
                (mode & 0400) != 0 ? "r" : "-",
                (mode & 0200) != 0 ? "w" : "-",
                (mode & 0100) != 0 ? "x" : "-"
        );
    }


    /**
     * 校验路径安全性（防御路径穿越攻击）
     */
    private void validatePathSafety(Path destPath, Path tempRoot) throws IOException {
        if (!destPath.startsWith(tempRoot)) {
            throw new IOException("检测到非法路径穿越: " + destPath);
        }
    }

    /**
     * 获取无扩展名的文件名
     */
    private String getFileNameWithoutExtension(String fileName) {
        // 检查是否是 .tar.gz 格式
        if (fileName.endsWith(".tar.gz")) {
            return fileName.substring(0, fileName.length() - 7);  // 去掉 ".tar.gz"
        }
        // 检查是否是 .tar 格式
        if (fileName.endsWith(".tar")) {
            return fileName.substring(0, fileName.length() - 4);  // 去掉 ".tar"
        }
        // 检查是否是 .zip 格式
        if (fileName.endsWith(".zip")) {
            return fileName.substring(0, fileName.length() - 4);  // 去掉 ".zip"
        }
        // 检查是否是 .7z 格式
        if (fileName.endsWith(".7z")) {
            return fileName.substring(0, fileName.length() - 3);  // 去掉 ".7z"
        }
        // 对其他普通文件名，直接返回去除扩展名的部分
        int dotIndex = fileName.lastIndexOf('.');
        return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
    }

    /*******************************************************本地和minio对比开始****************************************************************/


    /**********************************
     * 用途说明: 对比本地有minio没有的文件差异
     * 参数说明  taskId 节点ID
     * 参数说明  nodeId 任务ID
     * 参数说明  id     文件id
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
     ***********************************/
    @Override
    public Page<TsFiles> compareLocal(List<String> dataset, String nodeId, String taskId, Page<TsFiles> page) {
        // ==================== 1. 构建查询条件 ====================
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "node_id", "task_id", "is_file", "parent_id", "file_name",
                "file_size", "work_path", "backup_path", "upload_time"); // 移除无用字段

        if (StringUtils.isNoneEmpty(nodeId, taskId)) {
            queryWrapper.eq("node_id", nodeId)
                    .eq("task_id", taskId)
                    .isNotNull("work_path")
                    .ne("work_path", "")
                    .and(wq -> wq.isNull("backup_path").or().eq("backup_path", ""));
        } else {
            if (CollectionUtils.isEmpty(dataset)) {
                throw new IllegalArgumentException("dataset参数不可为空"); // 优化点9：提前校验参数
            }
            queryWrapper.in("id", dataset);
        }
        List<TsFiles> parentFiles = tsFilesMapper.selectList(queryWrapper);


        // 递归查询每个记录的子节点，并添加到 records 中
        List<TsFiles> allFiles = new ArrayList<>();
        if (StringUtils.isNotBlank(nodeId) && StringUtils.isNotBlank(taskId)) {
            for (TsFiles tsFiles : parentFiles) {
//                tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11：路径处理内聚
                //   如果备份路径为空 增加 将当前节点加入结果列表
                if (StringUtils.isEmpty(tsFiles.getBackupPath())) {
                    allFiles.add(tsFiles);
                }
            }
        } else {
            for (TsFiles tsFiles : parentFiles) {
//                tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11：路径处理内聚
                //   如果备份路径为空 增加 将当前节点加入结果列表
                if (StringUtils.isEmpty(tsFiles.getBackupPath()) && "FILE".equals(tsFiles.getIsFile())) {
                    allFiles.add(tsFiles);
                }
                // 查询该节点的所有子节点并递归添加
                if ("FOLDER".equals(tsFiles.getIsFile())) {
                    getChildFilesRecursiveLocal(tsFiles.getId(), allFiles);
                }
            }
        }


        // ==================== 3. 内存分页处理 ====================
        int total = allFiles.size();
        int pageSize = (int) page.getSize();
        int currentPage = (int) page.getCurrent();
        int fromIndex = (currentPage - 1) * pageSize;
        int toIndex = Math.min(fromIndex + pageSize, total);

        // 构建分页结果
        Page<TsFiles> resultPage = new Page<>(currentPage, pageSize, total);
        if (fromIndex < total) {
            resultPage.setRecords(allFiles.subList(fromIndex, toIndex));
        } else {
            resultPage.setRecords(Collections.emptyList());
        }
        return resultPage;

    }

    // 递归查询所有子节点
    private List<TsFiles> getChildFilesRecursiveLocal(String parentId, List<TsFiles> allFiles) {
        // 构建查询条件，获取当前parentId的所有子节点
        QueryWrapper<TsFiles> childQueryWrapper = new QueryWrapper<>();
        childQueryWrapper.eq("parent_id", parentId)
                .isNotNull("work_path")
                .ne("work_path", "")
                .and(wq -> wq.isNull("backup_path").or().eq("backup_path", ""));

        // 获取当前父节点的子节点
        List<TsFiles> children = tsFilesMapper.selectList(childQueryWrapper);

        if (children != null && !children.isEmpty()) {

            //allFiles.addAll(children);

            // 对每个子节点递归查询其子节点
            for (TsFiles child : children) {
                // child.setWorkPath(processingPath(child.getWorkPath(), child.getNodeId())); // 优化点11：路径处理内聚
                if (StringUtils.isEmpty(child.getBackupPath())) {
                    allFiles.add(child);
                }
                if ("FOLDER".equals(child.getIsFile())) {
                    List<TsFiles> childChildFiles = getChildFilesRecursiveLocal(child.getId(), allFiles);
                }
            }
        }
        return allFiles;
    }


    @Override
    public List<TsFiles> compareLocalList(List<String> dataset, String nodeId, String taskId) {

        // ==================== 1. 构建查询条件 ====================
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "node_id", "task_id", "is_file", "parent_id", "file_name",
                "file_size", "work_path", "backup_path", "upload_time"); // 移除无用字段

        if (StringUtils.isNoneEmpty(nodeId, taskId)) {
            queryWrapper.eq("node_id", nodeId)
                    .eq("task_id", taskId)
                    .isNotNull("work_path")
                    .ne("work_path", "")
                    .and(wq -> wq.isNull("backup_path").or().eq("backup_path", ""));
        } else {
            if (CollectionUtils.isEmpty(dataset)) {
                throw new IllegalArgumentException("dataset参数不可为空"); // 优化点9：提前校验参数
            }
            queryWrapper.in("id", dataset);
        }
        List<TsFiles> records = tsFilesMapper.selectList(queryWrapper);
        if (records == null) {
            return records;
        }

        // 递归查询每个记录的子节点，并添加到 records 中
        List<TsFiles> allFiles = new ArrayList<>();
        for (TsFiles tsFiles : records) {
            // tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11：路径处理内聚
            //   如果备份路径为空 增加 将当前节点加入结果列表
            if (StringUtils.isEmpty(tsFiles.getBackupPath())) {
                allFiles.add(tsFiles);

            }
            // 查询该节点的所有子节点并递归添加
            if ("FOLDER".equals(tsFiles.getIsFile())) {
                List<TsFiles> childFiles = getChildFilesRecursiveLocal(tsFiles.getId(), allFiles);
            }
        }
        return allFiles;
    }

    /**********************************
     * 用途说明: 对比本地没有但MinIO有的文件差异（优化版）
     * 优化点：
     * 1. 分页查询避免一次性加载全量数据
     * 2. 并行流加速数据转换
     * 3. 精简日志输出减少IO开销
     * 4. 添加数据库索引建议
     * 参数说明 taskId 节点ID
     * 参数说明 nodeId 任务ID
     * 参数说明 id     文件id
     * 返回值说明: 返回包含必要字段的DTO列表
     ***********************************/

    @Override
    public Page<TsFiles> compareMinio(List<String> dataset, String nodeId, String taskId, Page<TsFiles> page) {
        // ==================== 1. 构建查询条件 ====================
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "node_id", "task_id", "is_file", "parent_id", "file_name",
                "file_size", "work_path", "backup_path", "upload_time");


        if (StringUtils.isNoneEmpty(nodeId, taskId)) {
            // 场景1：根据 nodeId + taskId 查询（确保数据库有联合索引）
            queryWrapper.eq("node_id", nodeId)
                    .eq("task_id", taskId)
                    .isNotNull("backup_path")
                    .ne("backup_path", "")
                    .and(wq -> wq.isNull("work_path").or().eq("work_path", ""));
        } else {
            // 场景2：根据 id 列表查询（id字段需有索引）
            queryWrapper.in("id", dataset);
//                    .isNotNull("backup_path")
//                    .ne("backup_path", "")
//                    .and(wq -> wq.isNull("work_path").or().eq("work_path", ""));
        }
        List<TsFiles> parentFiles = tsFilesMapper.selectList(queryWrapper);
        // 递归查询每个记录的子节点，并添加到 records 中
        List<TsFiles> allFiles = new ArrayList<>();
        if (StringUtils.isNotBlank(nodeId) && StringUtils.isNotBlank(taskId)) {
            for (TsFiles tsFiles : parentFiles) {
                // tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11：路径处理内聚
                //   如果备份路径为空 增加 将当前节点加入结果列表
                if (StringUtils.isEmpty(tsFiles.getWorkPath())) {
                    allFiles.add(tsFiles);
                }
            }
        } else {
            for (TsFiles tsFiles : parentFiles) {
                //  tsFiles.setBackupPath(processingPath(tsFiles.getBackupPath(), tsFiles.getNodeId()));
                //   如果工作路径为空 增加 将当前节点加入结果列表
                if (StringUtils.isEmpty(tsFiles.getWorkPath())) {
                    allFiles.add(tsFiles);
                }
                // 查询该节点的所有子节点并递归添加
                if ("FOLDER".equals(tsFiles.getIsFile())) {
                    getChildFilesRecursiveMinio(tsFiles.getId(), allFiles);
                }
            }
        }


        // ==================== 3. 内存分页处理 ====================
        int total = allFiles.size();
        int pageSize = (int) page.getSize();
        int currentPage = (int) page.getCurrent();
        int fromIndex = (currentPage - 1) * pageSize;
        int toIndex = Math.min(fromIndex + pageSize, total);

        // 构建分页结果
        Page<TsFiles> resultPage = new Page<>(currentPage, pageSize, total);
        if (fromIndex < total) {
            resultPage.setRecords(allFiles.subList(fromIndex, toIndex));
        } else {
            resultPage.setRecords(Collections.emptyList());
        }
        return resultPage;
    }

    // 递归查询所有子节点
    private List<TsFiles> getChildFilesRecursiveMinio(String parentId, List<TsFiles> allFiles) {
        // 构建查询条件，获取当前parentId的所有子节点
        QueryWrapper<TsFiles> childQueryWrapper = new QueryWrapper<>();
        childQueryWrapper.eq("parent_id", parentId)
                .isNotNull("backup_path")
                .ne("backup_path", "")
                .and(wq -> wq.isNull("work_path").or().eq("work_path", ""));

        // 获取当前父节点的子节点
        List<TsFiles> children = tsFilesMapper.selectList(childQueryWrapper);

        if (children != null && !children.isEmpty()) {
            // 对每个子节点递归查询其子节点
            for (TsFiles child : children) {
                // child.setBackupPath(processingPath(child.getBackupPath(), child.getNodeId()));
                if (StringUtils.isEmpty(child.getWorkPath())) {
                    allFiles.add(child);
                }
                if ("FOLDER".equals(child.getIsFile())) {
                    List<TsFiles> childChildFiles = getChildFilesRecursiveLocal(child.getId(), allFiles);
                }
            }
        }
        return allFiles;
    }

    @Override
    public List<TsFiles> compareMinioList(List<String> dataset, String nodeId, String taskId) {
        // ==================== 1. 构建查询条件 ====================
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "node_id", "task_id", "is_file", "parent_id", "file_name",
                "file_size", "work_path", "backup_path", "upload_time");


        if (StringUtils.isNoneEmpty(nodeId, taskId)) {
            // 场景1：根据 nodeId + taskId 查询（确保数据库有联合索引）
            queryWrapper.eq("node_id", nodeId)
                    .eq("task_id", taskId)
                    .isNotNull("backup_path")
                    .ne("backup_path", "")
                    .and(wq -> wq.isNull("work_path").or().eq("work_path", ""));
        } else {
            // 场景2：根据 id 列表查询（id字段需有索引）
            queryWrapper.in("id", dataset);
//                    .isNotNull("backup_path")
//                    .ne("backup_path", "")
//                    .and(wq -> wq.isNull("work_path").or().eq("work_path", ""));
        }
        List<TsFiles> records = tsFilesMapper.selectList(queryWrapper);
        if (records == null) {
            return records;

        }
        // 递归查询每个记录的子节点，并添加到 records 中
        List<TsFiles> allFiles = new ArrayList<>();
        for (TsFiles tsFiles : records) {
            //tsFiles.setBackupPath(processingPath(tsFiles.getBackupPath(), tsFiles.getNodeId()));
            //   如果工作路径为空 增加 将当前节点加入结果列表
            if (StringUtils.isEmpty(tsFiles.getWorkPath())) {
                allFiles.add(tsFiles);

            }
            // 查询该节点的所有子节点并递归添加
            if ("FOLDER".equals(tsFiles.getIsFile())) {
                List<TsFiles> childFiles = getChildFilesRecursiveMinio(tsFiles.getId(), allFiles);
            }
        }
        return allFiles;
    }


    /**********************************
     * 用途说明: 对比本地和minio的文件差异
     * 参数说明  taskId 节点ID
     * 参数说明  nodeId 任务ID
     * 参数说明  id     文件id
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
     ***********************************/
    @Override
    public Page<TsFiles> compareMd5(List<String> dataset, String nodeId, String taskId, Page<TsFiles> page) {
        // 获取本地文件路径根目录和存储空间名称
        StorageSourceConfig filePathConfig = getStorageConfig("filePath", "local");
        StorageSourceConfig bucketConfig = getStorageConfig("bucketName", "minio");


        // ================ 1. 执行原始分页查询 ================
        QueryWrapper<TsFiles> queryWrapper = buildQueryWrapper(dataset, nodeId, taskId);
        Page<TsFiles> tsFilesPage = tsFilesMapper.selectPage(page, queryWrapper);

        if (StringUtils.isEmpty(nodeId) || StringUtils.isEmpty(taskId)) {
            if (tsFilesPage == null) {
                tsFilesPage = new Page<>();
                tsFilesPage.setRecords(new ArrayList<>()); // 确保 records 被初始化
            }

            List<TsFiles> records = tsFilesPage.getRecords();
            // 递归查询每个记录的子节点，并添加到 records 中
            List<TsFiles> allFiles = new ArrayList<>();
            for (TsFiles tsFiles : records) {
                // 将当前节点加入结果列表
                allFiles.add(tsFiles);
                // 查询该节点的所有子节点并递归添加
                if ("FOLDER".equals(tsFiles.getIsFile())) {
                    List<TsFiles> childFiles = getChildFilesRecursiveMd5(tsFiles.getId(), allFiles);
                }
            }
            tsFilesPage.setRecords(allFiles); // 同步到 tsFilesPage
            System.out.println("Updated records: " + allFiles);
        }


        // ================ 2. 过滤并处理符合条件的记录 ================
        List<TsFiles> filteredRecords = tsFilesPage.getRecords().stream()
                .filter(tsFile -> {
                    try {
                        // 计算本地文件MD5
                        File localFile = new File(filePathConfig.getValue() + tsFile.getWorkPath(), tsFile.getFileName());
                        String localMD5 = calculateMD5Data(new FileInputStream(localFile));

                        // 计算MinIO文件MD5

                        String minioMD5 = getMinioMD5Data(bucketConfig.getValue(), tsFile.getBackupPath(), tsFile.getFileName());

                        // 路径处理
                        //tsFile.setWorkPath(processingPath(tsFile.getWorkPath(), tsFile.getNodeId()));
                        // tsFile.setBackupPath(processingPath(tsFile.getBackupPath(), tsFile.getNodeId()));

                        // 设置MD5字段（即使不满足条件也保留字段）
                        tsFile.setLocatMd5(localMD5);
                        tsFile.setMinioMd5(minioMD5);

                        // 返回是否满足过滤条件
                        return StringUtils.isNoneEmpty(localMD5, minioMD5) && !localMD5.equals(minioMD5);
                    } catch (Exception e) {
                        LOGGER.error("MD5计算失败: {}", tsFile.getFileName(), e);
                        return false;
                    }
                })
                .collect(Collectors.toList());


        // ================ 3. 构建新的分页结果 ================
        Page<TsFiles> resultPage = new Page<>();
        resultPage.setRecords(filteredRecords);          // 过滤后的数据
        resultPage.setTotal(filteredRecords.size());     // 总记录数为当前页过滤后数量
        resultPage.setCurrent(page.getCurrent());        // 保持当前页码
        resultPage.setSize(page.getSize());              // 保持每页大小
        resultPage.setPages(                             // 重新计算总页数
                (filteredRecords.size() + page.getSize() - 1) / page.getSize()
        );

        return resultPage;
    }


    @Override
    public List<TsFiles> compareMd5List(List<String> dataset, String nodeId, String taskId) {
        // 获取本地文件路径根目录和存储空间名称
        StorageSourceConfig filePathConfig = getStorageConfig("filePath", "local");
        StorageSourceConfig bucketConfig = getStorageConfig("bucketName", "minio");

        // ================ 1. 执行原始分页查询 ================
        QueryWrapper<TsFiles> queryWrapper = buildQueryWrapper(dataset, nodeId, taskId);
        List<TsFiles> records = tsFilesMapper.selectList(queryWrapper);

        if (StringUtils.isEmpty(nodeId) || StringUtils.isEmpty(taskId)) {
            if (records == null) {
                return records;
            }

            // 递归查询每个记录的子节点，并添加到 records 中
            List<TsFiles> allFiles = new ArrayList<>();
            for (TsFiles tsFiles : records) {
                // 将当前节点加入结果列表
                allFiles.add(tsFiles);
                // 查询该节点的所有子节点并递归添加
                if ("FOLDER".equals(tsFiles.getIsFile())) {
                    List<TsFiles> childFiles = getChildFilesRecursiveMd5(tsFiles.getId(), allFiles);
                }
            }
        }

        // ================ 2. 过滤并处理符合条件的记录 ================
        List<TsFiles> filteredRecords = records.stream()
                .filter(tsFile -> {
                    try {
                        // 计算本地文件MD5
                        File localFile = new File(filePathConfig.getValue() + tsFile.getWorkPath(), tsFile.getFileName());
                        String localMD5 = calculateMD5Data(new FileInputStream(localFile));

                        // 计算MinIO文件MD5

                        String minioMD5 = getMinioMD5Data(bucketConfig.getValue(), tsFile.getBackupPath(), tsFile.getFileName());

                        // 路径处理
                        //   tsFile.setWorkPath(processingPath(tsFile.getWorkPath(), tsFile.getNodeId()));
                        //  tsFile.setBackupPath(processingPath(tsFile.getBackupPath(), tsFile.getNodeId()));

                        // 设置MD5字段（即使不满足条件也保留字段）
                        tsFile.setLocatMd5(localMD5);
                        tsFile.setMinioMd5(minioMD5);

                        // 返回是否满足过滤条件
                        return StringUtils.isNoneEmpty(localMD5, minioMD5) && !localMD5.equals(minioMD5);
                    } catch (Exception e) {
                        LOGGER.error("MD5计算失败: {}", tsFile.getFileName(), e);
                        return false;
                    }
                })
                .collect(Collectors.toList());

        return filteredRecords;
    }


    // 递归查询所有子节点
    private List<TsFiles> getChildFilesRecursiveMd5(String parentId, List<TsFiles> allFiles) {
        // 构建查询条件，获取当前parentId的所有子节点
        QueryWrapper<TsFiles> childQueryWrapper = new QueryWrapper<>();
        childQueryWrapper.eq("parent_id", parentId)
                .isNotNull("backup_path")
                .ne("backup_path", "")
                .isNotNull("work_path")
                .ne("work_path", "");

        // 获取当前父节点的子节点
        List<TsFiles> children = tsFilesMapper.selectList(childQueryWrapper);

        if (children != null && !children.isEmpty()) {
            allFiles.addAll(children);
            // 对每个子节点递归查询其子节点
            for (TsFiles child : children) {
                if ("FOLDER".equals(child.getIsFile())) {
                    List<TsFiles> childChildFiles = getChildFilesRecursiveLocal(child.getId(), allFiles);
                }
            }
        }
        return allFiles;
    }

    // ================ 辅助方法：构建查询条件 ================
    private QueryWrapper<TsFiles> buildQueryWrapper(List<String> dataset, String nodeId, String taskId) {
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "node_id", "task_id", "is_file", "parent_id", "file_name", "file_size", "work_path", "backup_path", "upload_time");

        if (StringUtils.isNoneEmpty(nodeId, taskId)) {
            //查询数据库中 minio路径有值 本地没有值的所有数据 根据nodeId，taskid
            queryWrapper.eq("node_id", nodeId)
                    .eq("task_id", taskId)
                    .isNotNull("backup_path")
                    .ne("backup_path", "")
                    .isNotNull("work_path")
                    .ne("work_path", "");
            System.out.println("完整SQL: " + queryWrapper.getCustomSqlSegment());
        } else {
            //查询数据库中 minio路径有值 本地没有值的所有数据 根据id
            queryWrapper.in("id", dataset)
                    .isNotNull("backup_path")
                    .ne("backup_path", "")
                    .isNotNull("work_path")
                    .ne("work_path", "");
        }
        return queryWrapper;
    }

    // 辅助方法：获取 MinIO 文件 MD5
    private String getMinioMD5Data(String bucketName, String path, String name) {
        AbstractBaseFileService<?> minioService = storageSourceContext.getByStorageKey("minio");
        String key = normalizePath(path) + name;

        try {
            ObjectMetadata metadata = minioService.getObjectMetadata(bucketName, key);
            String eTag = metadata.getETag();

            // 单部分上传的 ETag 是完整 MD5，可直接使用
            if (eTag != null && !eTag.contains("-")) {
                return eTag.replace("\"", "");
            } else {
                // 多部分上传需重新计算
                try (InputStream stream = minioService.getObject(bucketName, key).getObjectContent()) {
                    return calculateMD5Data(stream);
                }
            }
        } catch (Exception e) {
            LOGGER.error("Failed to get MinIO MD5: {}", key, e);
            return null;
        }
    }

    // 优化后的 MD5 计算方法（线程安全）
    public String calculateMD5Data(InputStream inputStream) throws IOException, NoSuchAlgorithmException {
        try (InputStream is = inputStream) { // 确保流关闭
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                md.update(buffer, 0, bytesRead);
            }
            return Hex.encodeHexString(md.digest());
        }
    }

    // 辅助方法：获取存储配置
    private StorageSourceConfig getStorageConfig(String name, String type) {
        return storageSourceConfigMapper.selectOne(
                new QueryWrapper<StorageSourceConfig>().eq("name", name).eq("type", type)
        );
    }

    /**
     * 确保路径格式为以 "/" 开头和结尾（例如 "/data/test/"）
     * 若路径为空或非法，返回根路径 "/"
     */
    private String ensurePathFormat(String path) {
        if (StringUtils.isBlank(path)) {
            return "/";
        }
        // 统一替换反斜杠为斜杠（兼容 Windows 路径）
        String normalized = path.replaceAll("\\\\", "/");
        // 确保以 "/" 开头
        if (!normalized.startsWith("/")) {
            normalized = "/" + normalized;
        }
        // 确保以 "/" 结尾
        if (!normalized.endsWith("/")) {
            normalized += "/";
        }
        return normalized;
    }

/*******************************************************本地和minio对比结束****************************************************************/


    /******************************************************对比结束以后开始同步***************************************************************************************/
    /**********************************
     * 用途说明: 将文件上传到备份空间
     * 参数说明  parameter 数据集合
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
     ***********************************/
    @Override
    public Boolean uploadToBackup(Parameter parameter) {
        Boolean value = false;

        // 设置当前时间
        LocalDateTime now = LocalDateTime.now();
        // 转换为 Timestamp
        Timestamp currentTime = Timestamp.valueOf(now);


        StorageSourceConfig storageSourceConfig = getStorageConfig("filePath", "local");

        StorageSourceConfig storageSourceConfig1 = getStorageConfig("bucketName", "minio");


        List<ParameterList> parameterLists = parameter.getParameterLists();

        //循环一条一条上传
        for (ParameterList parameterListData : parameterLists) {
            if ("FOLDER".equals(parameterListData.getType())) {
                String name = parameterListData.getName();
                String path = parameterListData.getPath();
                // 处理路径格式
                path = normalizePath(path);


                String localPath = storageSourceConfig.getValue() + path;//localPath是本地的路径
                String minioPath = path;//// MinIO上传目标路径
                // 使用Java NIO的Path类来构造压缩文件的完整路径，存储在localPath目录下
                // 查询当前文件记录
                TsFiles currentFile = getFileRecord(name, path);
                if (currentFile == null) return false;

                // 递归创建父文件夹
                boolean parentsCreated = createParentFolders(currentFile.getParentId());
                if (!parentsCreated) {
                    LOGGER.error("父文件夹创建失败");
                    return false;
                }

                //新增节点的时候 创建文件夹
                NewFolderRequest newFolderRequest = new NewFolderRequest();
                newFolderRequest.setName(currentFile.getFileName());//新建的文件夹名称,示例值(/a/b/c)
                newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问，则支持请求密码,示例值(123456)
                newFolderRequest.setPath(minioPath);//请求路径,示例值(/)
                newFolderRequest.setStorageKey("minio");//存储源 key,示例值(local minio)
                AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey());
                boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName());
                LOGGER.info("同步本地到minio文件夹路径加名称" + newFolderRequest.getPath() + newFolderRequest.getName());

                //如果文件夹创建成功
                if (flag) {
                    currentFile.setUpdateTime(currentTime);
                    currentFile.setBackupPath(minioPath);
                    int valueAdded = tsFilesMapper.updateById(currentFile);
                    if (valueAdded == 1) {
                        LOGGER.info("同步本地到minio,minio文件夹创建成功,表结构修改成功。");
                        value = true;
                    } else {
                        LOGGER.error("同步本地到minio,minio文件夹创建成功，表结构修改失败");
                        value = false;
                    }

                } else {
                    LOGGER.error("同步本地到minio,minio文件夹创建失败");
                    value = false;
                }
            } else {

                String name = parameterListData.getName();
                String path = parameterListData.getPath();
                String size = parameterListData.getSize();
                // 处理路径格式
                path = normalizePath(path);

                String localPath = storageSourceConfig.getValue() + path;//localPath是本地的路径
                String minioPath = path;//// MinIO上传目标路径

                // 使用Java NIO的Path类来构造压缩文件的完整路径，存储在localPath目录下
                Path FilePath = Paths.get(localPath, name);
                // 4. 上传压缩文件到MinIO
                File zipFile = FilePath.toFile(); // 获取本地文件

                // 查询当前文件记录
                TsFiles currentFile = getFileRecord(name, path);
                if (currentFile == null) {
                    return false;
                }

                // 递归创建父文件夹
                boolean parentsCreated = createParentFolders(currentFile.getParentId());
                if (!parentsCreated) {
                    LOGGER.error("父文件夹创建失败");
                    return false;
                }
                //把本地文件上传到minio
                AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey("minio");
                boolean flag1 = fileService.UploadFiles(storageSourceConfig1.getValue(), minioPath + name, zipFile);
                LOGGER.info("同步本地到minio文件路径加名称" + storageSourceConfig1.getValue() + minioPath + name);

                //如果上传成功  修改 表结构
                if (flag1) {
                    currentFile.setUpdateTime(currentTime);
                    currentFile.setBackupPath(minioPath);
                    int valueAdded = tsFilesMapper.updateById(currentFile);
                    if (valueAdded == 1) {
                        LOGGER.info("同步本地到minio,minio文件创建成功,表结构修改成功。");
                        value = true;
                    } else {
                        LOGGER.error("同步本地到minio,minio文件创建成功，表结构修改失败");
                        value = false;
                    }
                } else {
                    LOGGER.error("同步本地到minio,minio文件创建失败");
                    value = false;
                }
            }
        }
        return value;
    }

    // 规范化路径格式
    private String normalizePath(String path) {
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        if (!path.endsWith("/")) {
            path += "/";
        }
        return path.replace("//", "/");
    }

    // 查询文件记录
    private TsFiles getFileRecord(String name, String path) {
        QueryWrapper<TsFiles> query = new QueryWrapper<>();
        query.eq("file_name", name)
                .eq("work_path", path);
        return tsFilesMapper.selectOne(query);
    }

    // 递归创建minio父文件夹
    private boolean createParentFolders(String parentId) {
        TsFiles parentFolder = tsFilesMapper.selectById(parentId);
        if (parentFolder == null || "00".equals(parentId)) {
            return true; // 到达根目录
        }

        // 先递归处理上级
        boolean ancestorsCreated = createParentFolders(parentFolder.getParentId());
        if (!ancestorsCreated) {
            return false;
        }

        // 检查当前文件夹是否已创建
        if (StringUtils.isEmpty(parentFolder.getBackupPath())) {
            boolean folderCreated = createMinioFolder(parentFolder);
            if (!folderCreated) return false;
        }
        return true;
    }

    // 创建MinIO文件夹
    private boolean createMinioFolder(TsFiles folder) {
        try {
            // 设置当前时间
            LocalDateTime now = LocalDateTime.now();
            // 转换为 Timestamp
            Timestamp currentTime = Timestamp.valueOf(now);
            AbstractBaseFileService<?> minioService = storageSourceContext.getByStorageKey("minio");
            //String folderPath = folder.getWorkPath() + folder.getFileName() + "/";
            String folderPath = folder.getWorkPath() + folder.getFileName();

            //新增节点的时候 创建文件夹
            NewFolderRequest newFolderRequest = new NewFolderRequest();
            newFolderRequest.setName(folder.getFileName());//新建的文件夹名称,示例值(/a/b/c)
            newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问，则支持请求密码,示例值(123456)
            newFolderRequest.setPath(folder.getWorkPath());//请求路径,示例值(/)
            newFolderRequest.setStorageKey("minio");//存储源 key,示例值(local minio)
            AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey());
            boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName());
            if (flag) {
                folder.setUpdateTime(currentTime);
                folder.setBackupPath(folder.getWorkPath());
                tsFilesMapper.updateById(folder);
                LOGGER.info("创建文件夹成功：{}", folderPath);
                return true;
            }
            return false;
        } catch (Exception e) {
            LOGGER.error("创建文件夹失败：{}", folder.getFileName(), e);
            return false;
        }
    }


    @Override
    @Async("asyncExecutor")
    public void automaticFileBackupAsync(String taskId, String nodeId) throws IOException {
        try {
            // 执行实际备份逻辑
            this.automaticFileBackup(taskId, nodeId);
        } finally {
            // 生成唯一Key
            String asyncKey = taskStatusHolder.generateKey(taskId, nodeId);
            // 无论成功失败都标记完成
            taskStatusHolder.finishTask(asyncKey);
            WebSocketServer.sendMessageTo("文件自动备份完成", "taskId_"+taskId+"_"+"nodeId_"+nodeId);
        }
    }

    @Override
    @Async("asyncExecutor")
    public void automaticFileBackupAsyncByIds(List<String> dataset,String taskId,String nodeId) throws IOException {
        try {
            // 执行实际备份逻辑
            this.automaticFileBackupByIds(dataset);
        } finally {
            // 生成唯一Key
            String asyncKey = taskStatusHolder.generateKey(taskId, nodeId);
            // 无论成功失败都标记完成
            taskStatusHolder.finishTask(asyncKey);
            WebSocketServer.sendMessageTo("文件自动备份完成", "taskId_"+taskId+"_"+"nodeId_"+nodeId);
        }
    }


    /**********************************
     * 用途说明: 文件自动备份通过节点和任务
     * 参数说明  taskId 节点ID
     * 参数说明  nodeId 任务ID
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
     ***********************************/
    @Override
    public String automaticFileBackup(String taskId, String nodeId) {
        //首先查询节点下面所有 备份路径为空的数据
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("node_id", nodeId);
        queryWrapper.eq("task_id", taskId);
        queryWrapper.and(wrapper -> wrapper.isNull("backup_path").or().eq("backup_path", ""));
        queryWrapper.isNotNull("work_path").ne("work_path", "");
        List<TsFiles> tsFilelist = tsFilesMapper.selectList(queryWrapper);
        int FileCount = 0, FolderCount = 0;
        int BackupsFileCount = 0, BackupsFolderCount = 0;

        if (tsFilelist.isEmpty()) {
            return "本地有新增文件 " + FileCount + " 个, 文件夹 " + FolderCount + " 个, 已将 " + BackupsFileCount + " 个文件,  " + BackupsFolderCount + " 个文件夹备份成功";
        }

        for (TsFiles tsFiles : tsFilelist) {
            ParameterList parameterList = new ParameterList();
            Parameter fileParameter = new Parameter();
            Parameter FolderParameter = new Parameter();
            List<ParameterList> fileParameterlist = new ArrayList<>();
            List<ParameterList> FolderParameterlist = new ArrayList<>();

            if ("FILE".equals(tsFiles.getIsFile())) {
                parameterList.setName(tsFiles.getFileName());
                parameterList.setPath(tsFiles.getWorkPath());
                parameterList.setSize(tsFiles.getFileSize());
                parameterList.setType(tsFiles.getIsFile());
                fileParameterlist.add(parameterList);
                fileParameter.setParameterLists(fileParameterlist);
                Boolean value = uploadToBackup(fileParameter);
                if (value) {
                    BackupsFileCount++;
                }
                FileCount++;
            } else {
                parameterList.setName(tsFiles.getFileName());
                parameterList.setPath(tsFiles.getWorkPath());
                parameterList.setSize(tsFiles.getFileSize());
                parameterList.setType(tsFiles.getIsFile());
                FolderParameterlist.add(parameterList);
                FolderParameter.setParameterLists(FolderParameterlist);
                Boolean value = uploadToBackup(FolderParameter);
                if (value) {
                    BackupsFolderCount++;
                }
                FolderCount++;
            }
        }
        return "本地有新增文件 " + FileCount + " 个, 新增文件夹 " + FolderCount + " 个, 已将 " + BackupsFileCount + " 个文件,  " + BackupsFolderCount + " 个文件夹备份成功";
    }

    /**********************************
     * 用途说明: 文件自动备份通过ID
     * 参数说明  taskId 节点ID
     * 参数说明  nodeId 任务ID
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
     ***********************************/
    public String automaticFileBackupByIds(List<String> dataset) {

        List<TsFiles> allFiles = new ArrayList<>();
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "node_id", "task_id", "is_file", "parent_id", "file_name",
                "file_size", "work_path", "backup_path", "upload_time"); // 移除无用字段
        queryWrapper.in("id", dataset);
        List<TsFiles> records = tsFilesMapper.selectList(queryWrapper);
        for (TsFiles tsFiles : records) {
            //   如果备份路径为空 增加 将当前节点加入结果列表
            if (StringUtils.isEmpty(tsFiles.getBackupPath())) {
                allFiles.add(tsFiles);
            }
            // 查询该节点的所有子节点并递归添加
            if ("FOLDER".equals(tsFiles.getIsFile())) {
                List<TsFiles> childFiles = getChildFilesRecursiveLocal(tsFiles.getId(), allFiles);
            }
        }
        int FileCount = 0, FolderCount = 0;
        int BackupsFileCount = 0, BackupsFolderCount = 0;

        if (allFiles.isEmpty()) {
            return "本地有新增文件 " + FileCount + " 个, 文件夹 " + FolderCount + " 个, 已将 " + BackupsFileCount + " 个文件,  " + BackupsFolderCount + " 个文件夹备份成功";
        }

        for (TsFiles tsFiles : allFiles) {
            ParameterList parameterList = new ParameterList();
            Parameter fileParameter = new Parameter();
            Parameter FolderParameter = new Parameter();
            List<ParameterList> fileParameterlist = new ArrayList<>();
            List<ParameterList> FolderParameterlist = new ArrayList<>();

            if ("FILE".equals(tsFiles.getIsFile())) {
                parameterList.setName(tsFiles.getFileName());
                parameterList.setPath(tsFiles.getWorkPath());
                parameterList.setSize(tsFiles.getFileSize());
                parameterList.setType(tsFiles.getIsFile());
                fileParameterlist.add(parameterList);
                fileParameter.setParameterLists(fileParameterlist);
                Boolean value = uploadToBackup(fileParameter);
                if (value) {
                    BackupsFileCount++;
                }
                FileCount++;
            } else {
                parameterList.setName(tsFiles.getFileName());
                parameterList.setPath(tsFiles.getWorkPath());
                parameterList.setSize(tsFiles.getFileSize());
                parameterList.setType(tsFiles.getIsFile());
                FolderParameterlist.add(parameterList);
                FolderParameter.setParameterLists(FolderParameterlist);
                Boolean value = uploadToBackup(FolderParameter);
                if (value) {
                    BackupsFolderCount++;
                }
                FolderCount++;
            }
        }
        return "本地有新增文件 " + FileCount + " 个, 新增文件夹 " + FolderCount + " 个, 已将 " + BackupsFileCount + " 个文件,  " + BackupsFolderCount + " 个文件夹备份成功";
    }


    /**********************************
     * 用途说明: 从备份空间下载到工作空间
     * 参数说明  parameter 数据集合
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
     ***********************************/
    @Override
    public Boolean downloadToLocal(Parameter parameter) {
        Boolean value = true;

        // 设置当前时间
        LocalDateTime now = LocalDateTime.now();
        // 转换为 Timestamp
        Timestamp currentTime = Timestamp.valueOf(now);

        //上传人是当前登录人
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = (LoginUser) authentication.getPrincipal();

        StorageSourceConfig storageSourceConfig = getStorageConfig("filePath", "local");

        StorageSourceConfig storageSourceConfig1 = getStorageConfig("bucketName", "minio");


        List<ParameterList> parameterLists = parameter.getParameterLists();
        for (ParameterList parameterListData : parameterLists) {
            //创建文件夹
            if ("FOLDER".equals(parameterListData.getType())) {
                String name = parameterListData.getName();
                String path = parameterListData.getPath();
                // 处理路径格式
                path = normalizePath(path);

                //通过名称和minio路径查询表结构的数据
                QueryWrapper<TsFiles> queryWrapperFile = new QueryWrapper<>();
                queryWrapperFile.eq("file_name", name);
                queryWrapperFile.eq("backup_path", path);
                TsFiles tsFiles = tsFilesMapper.selectOne(queryWrapperFile);
                if (tsFiles == null) {
                    return false;
                }
                LOGGER.info("同步minio到本地,查询的文件夹对象", tsFiles);

                // 递归创建父文件夹
                boolean parentsCreated = createLocalParentFolders(tsFiles.getParentId());
                if (!parentsCreated) {
                    LOGGER.error("父文件夹创建失败");
                    return false;
                }


                //新增节点的时候 创建文件夹
                NewFolderRequest newFolderRequest = new NewFolderRequest();
                newFolderRequest.setName(tsFiles.getFileName());//新建的文件夹名称,示例值(/a/b/c)
                newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问，则支持请求密码,示例值(123456)
                newFolderRequest.setPath(path);//请求路径,示例值(/)
                newFolderRequest.setStorageKey("local");//存储源 key,示例值(local minio)
                AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey());
                boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName());
                LOGGER.info("同步minio到本地文件夹路径加名称" + newFolderRequest.getPath() + newFolderRequest.getName());

                //如果文件夹创建成功
                if (flag) {
                    tsFiles.setUpdateTime(currentTime);
                    tsFiles.setWorkPath(path);
                    int valueAdded = tsFilesMapper.updateById(tsFiles);
                    if (valueAdded == 1) {
                        LOGGER.info("同步minio到本地,本地文件夹创建成功,表结构修改成功。");
                        value = true;
                    } else {
                        LOGGER.error("同步minio到本地,本地文件夹创建成功，表结构修改失败");
                        value = false;
                    }

                } else {
                    LOGGER.error("同步minio到本地,本地文件夹创建失败");
                    value = false;
                }

            } else {
                //如果是文件
                String name = parameterListData.getName();
                String path = parameterListData.getPath();
                String size = parameterListData.getSize();
                // 处理路径格式
                path = normalizePath(path);

                //通过名称和minio路径查询表结构的数据
                QueryWrapper<TsFiles> queryWrapperFile = new QueryWrapper<>();
                queryWrapperFile.eq("file_name", name);
                queryWrapperFile.eq("backup_path", path);
                TsFiles tsFiles = tsFilesMapper.selectOne(queryWrapperFile);
                if (tsFiles == null) {
                    return false;
                }
                // 递归创建父文件夹
                boolean parentsCreated = createLocalParentFolders(tsFiles.getParentId());
                if (!parentsCreated) {
                    LOGGER.error("父文件夹创建失败");
                    return false;
                }

                String localPath = storageSourceConfig.getValue() + path + name;//localPath是本地的路径
                String minioPath = path + name;//// MinIO上传目标路径

                FileOutputStream outputStream = null;   // 输出流
                S3ObjectInputStream inputStream = null; // 输入流
                S3Object s3Object = null;
                try {
                    AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey("minio");
                    // 获取 MinIO 文件对象
                    s3Object = fileService.getObject(storageSourceConfig1.getValue(), minioPath);
                    // 获取文件输入流
                    inputStream = s3Object.getObjectContent();

                    // 创建本地文件
                    File localFile = new File(localPath);
                    if (!localFile.getParentFile().exists()) {
                        localFile.getParentFile().mkdirs(); // 创建父目录
                    }

                    // 将文件内容写入本地文件
                    outputStream = new FileOutputStream(localFile);
                    IOUtils.copy(inputStream, outputStream);
                    //文件下载成功以后 修改表结构
                    tsFiles.setUpdateTime(currentTime);
                    tsFiles.setWorkPath(path);
                    int valueAdded = tsFilesMapper.updateById(tsFiles);
                    if (valueAdded == 1) {
                        LOGGER.info("同步minio到本地,本地文件创建成功,表结构修改成功。");
                        LOGGER.info("文件下载成功：" + localPath);
                        value = true;
                    } else {
                        LOGGER.error("同步minio到本地,本地文件创建成功，表结构修改失败");
                        value = false;
                    }
                } catch (Exception e) {
                    LOGGER.error("文件下载失败：" + e.getMessage());
                } finally {
                    // 关闭流
                    if (inputStream != null) {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            LOGGER.error("关闭输入流失败：" + e.getMessage());
                        }
                    }
                    if (outputStream != null) {
                        try {
                            outputStream.close();
                        } catch (IOException e) {
                            LOGGER.error("关闭输出流失败：" + e.getMessage());
                        }
                    }
                    if (s3Object != null) {
                        try {
                            s3Object.close();
                        } catch (IOException e) {
                            LOGGER.error("关闭 S3Object 失败：" + e.getMessage());
                        }
                    }
                }
            }
        }
        return value;
    }


    // 递归创建本地父文件夹
    private boolean createLocalParentFolders(String parentId) {
        TsFiles parentFolder = tsFilesMapper.selectById(parentId);
        if (parentFolder == null || "00".equals(parentId)) {
            return true; // 到达根目录
        }

        // 先递归处理上级
        boolean ancestorsCreated = createLocalParentFolders(parentFolder.getParentId());
        if (!ancestorsCreated) return false;

        // 检查当前文件夹是否已创建
        if (StringUtils.isEmpty(parentFolder.getWorkPath())) {
            boolean folderCreated = createLocalFolder(parentFolder);
            if (!folderCreated) return false;
        }
        return true;
    }

    // 创建本地文件夹
    private boolean createLocalFolder(TsFiles folder) {
        try {
            // 设置当前时间
            LocalDateTime now = LocalDateTime.now();
            // 转换为 Timestamp
            Timestamp currentTime = Timestamp.valueOf(now);
            //String folderPath = folder.getWorkPath() + folder.getFileName() + "/";
            String folderPath = folder.getBackupPath() + folder.getFileName();

            //新增节点的时候 创建文件夹
            NewFolderRequest newFolderRequest = new NewFolderRequest();
            newFolderRequest.setName(folder.getFileName());//新建的文件夹名称,示例值(/a/b/c)
            newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问，则支持请求密码,示例值(123456)
            newFolderRequest.setPath(folder.getBackupPath());//请求路径,示例值(/)
            newFolderRequest.setStorageKey("local");//存储源 key,示例值(local minio)
            AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey());
            boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName());
            if (flag) {
                folder.setUpdateTime(currentTime);
                folder.setWorkPath(folder.getBackupPath());
                tsFilesMapper.updateById(folder);
                LOGGER.info("创建文件夹成功：{}", folderPath);
                return true;
            }
            return false;
        } catch (Exception e) {
            LOGGER.error("创建文件夹失败：{}", folder.getFileName(), e);
            return false;
        }
    }

    /*********************************************************************************************************************************************/


    /**********************************************************移动文件或者文件夹结束***********************************************************************************/
    /**
     * 移动文件或文件夹
     * 参数说明 newPath 新路径
     * 参数说明 oldpaths 原路径
     * 参数说明 ParentId 父ID
     * 参数说明 newFileName
     * 参数说明 Rename 重命名的文件名称
     * 参数说明 type 覆盖还是重命名  0 1
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String moveFileFolder(MoveCopyFileFolderRequest reques) throws IOException {
        // 1. 获取存储根路径
        StorageSourceConfig config = getStorageConfig("filePath", "local");


        Path rootPath = Paths.get(config.getValue());

        String newPath = reques.getNewPath();
        String oldpaths = reques.getOldpaths();
        String parentId = reques.getParentId();
        String rename = reques.getRename();
        String type = reques.getType();
        String[] newFileNames = reques.getNewFileName().split(",");
        // 数组转集合
        List<String> newFileNameList = Arrays.asList(newFileNames);
        // 2. 构建目标父目录的物理路径（如 E:\yun\22）
        Path targetParentPhysicalPath = buildPhysicalPath(newPath, rootPath);

        // 3. 循环处理所有选中的文件/文件夹
        for (String fileName : newFileNameList) {
            // 构建原文件物理路径（如 E:\yun\11\file.txt）
            Path sourcePath = buildPhysicalPath(oldpaths + "/" + fileName, rootPath);
            if (!Files.exists(sourcePath)) {
                throw new IOException("源文件不存在: " + sourcePath);
            }

            // ==== 新增关键校验 ====
            // 1. 禁止移动到自身
            if (sourcePath.equals(targetParentPhysicalPath)) {
                throw new IllegalArgumentException("不能移动到自己所在目录");
            }

            // 2. 禁止文件夹移动到子目录
            if (Files.isDirectory(sourcePath)) {
                if (targetParentPhysicalPath.startsWith(sourcePath)) {
                    throw new IllegalArgumentException("禁止将文件夹移动到自己内部");
                }
            }

            // 生成目标文件名（处理重命名和冲突）
            String targetFileName = generateTargetFileName(targetParentPhysicalPath, fileName, rename, type);
            Path targetPath = targetParentPhysicalPath.resolve(targetFileName);

            // 3. 再次检查自移动  如果是覆盖不需要校验这个
            if ("1".equals(type)) {
                if (sourcePath.equals(targetPath)) {
                    throw new IllegalArgumentException("不能移动到自己当前位置");
                }
            }
            // 执行移动操作
            moveFile(sourcePath, targetPath, type);

            // 更新数据库记录
            updateDatabase(parentId, oldpaths, fileName, newPath, targetFileName, type);
        }
        return "移动成功";
    }


    private String generateTargetFileName(Path targetParentPhysicalPath, String originalName, String rename, String type) {
        if ("1".equals(type)) {
            // 重命名模式：直接使用用户指定名称
            return rename;
        } else {
//            // 覆盖模式：处理文件名冲突（自动添加序号）
//            String baseName = originalName.lastIndexOf('.') != -1 ?
//                    originalName.substring(0, originalName.lastIndexOf('.')) : originalName;
//            String extension = originalName.lastIndexOf('.') != -1 ?
//                    originalName.substring(originalName.lastIndexOf('.')) : "";
//
//            int counter = 1;
//            String newName = originalName;
//            while (Files.exists(targetParentPhysicalPath.resolve(newName))) {
//                newName = baseName + "(" + counter + ")" + extension;
//                counter++;
//            }
            return originalName;
        }
    }

    /**
     * 构建完整的物理路径（基于数据库中的 work_path）
     */
    private Path buildPhysicalPath(String dbPath, Path root) {
        // 统一替换所有斜杠为系统分隔符（兼容不同系统）
        String systemPath = dbPath.replace("/", File.separator)
                .replace("\\", File.separator);

        // 处理根路径特殊情况（如 "/" 或空字符串）
        if (systemPath.equals(File.separator) || systemPath.isEmpty()) {
            return root;
        }

        // 修复正则表达式：使用 Pattern.quote 处理特殊字符
        String separatorRegex = Pattern.quote(File.separator);
        systemPath = systemPath.replaceAll("^" + separatorRegex + "+", "");

        try {
            Path resolvedPath = root.resolve(systemPath).normalize();

            // 二次校验防止路径越狱
            if (!resolvedPath.startsWith(root)) {
                throw new SecurityException("非法路径访问: " + dbPath);
            }
            return resolvedPath;
        } catch (InvalidPathException e) {
            throw new IllegalArgumentException("无效路径格式: " + dbPath, e);
        }
    }


    /**
     * 执行文件移动操作
     */
    private void moveFile(Path sourcePath, Path targetPath, String type) throws IOException {
        if ("0".equals(type)) {
            // 覆盖模式：强制替换目标文件
            Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
        } else {
            // 重命名模式：检查目标文件是否存在
            if (Files.exists(targetPath)) {
                throw new IOException("目标文件已存在：" + targetPath);
            }
            Files.move(sourcePath, targetPath);
        }

        // 移动后检查原路径是否为空，若为空则重新创建空文件夹（可选）
        if (isPathEmpty(sourcePath.getParent())) {
            Files.createDirectories(sourcePath.getParent());
        }
    }

    /**
     * 更新数据库中的父ID和路径
     */
    private void updateDatabase(String parentId, String oldpaths, String fileName,
                                String newPath, String targetFileName, String type) {
        // 1. 构建完整路径
        String oldWorkPath = oldpaths.endsWith("/") ? oldpaths : oldpaths + "/";
        String newWorkPath = newPath.endsWith("/") ? newPath : newPath + "/";

        // 2. 查询原记录
        TsFiles fileRecord = tsFilesMapper.selectOne(
                new QueryWrapper<TsFiles>().eq("work_path", oldWorkPath)
                        .eq("file_name", fileName));
        if (fileRecord == null) {
            throw new RuntimeException("数据库记录不存在：" + oldWorkPath + fileName);
        }

        // 3. 查询目标位置是否已存在记录
        TsFiles newfileRecord = tsFilesMapper.selectOne(
                new QueryWrapper<TsFiles>().eq("work_path", newWorkPath)
                        .eq("file_name", targetFileName));

        // 4. 根据 type 处理记录
        if ("0".equals(type)) {
            // 覆盖模式
            if (newfileRecord != null) {
                // 更新目标记录
                newfileRecord.setParentId(parentId);
                newfileRecord.setWorkPath(newWorkPath);
                newfileRecord.setFileName(targetFileName);
                newfileRecord.setUpdateTime(new Timestamp(System.currentTimeMillis())); // 更新时间
                tsFilesMapper.updateById(newfileRecord);
//                // 删除原记录  覆盖的时候不用删除原来的记录
//                tsFilesMapper.deleteById(fileRecord.getId());
            } else {
                // 插入新记录
                TsFiles newRecord = new TsFiles();
                newRecord.setNodeId(fileRecord.getNodeId());
                newRecord.setTaskId(fileRecord.getTaskId());
                newRecord.setIsFile(fileRecord.getIsFile());
                newRecord.setParentId(parentId);
                newRecord.setFileName(targetFileName);
                newRecord.setWorkPath(newWorkPath);
                newRecord.setBackupPath(fileRecord.getBackupPath());
                newRecord.setFileSize(fileRecord.getFileSize());
                newRecord.setKeywords(fileRecord.getKeywords());
                newRecord.setDescription(fileRecord.getDescription());
                newRecord.setUploadTime(new Timestamp(System.currentTimeMillis())); // 上传时间
                newRecord.setUploader(fileRecord.getUploader()); // 上传人
                tsFilesMapper.insert(newRecord);
                // 如果是文件夹，递归删除子项
                if ("FOLDER".equals(fileRecord.getIsFile())) {
                    // 先移动子项再删除原记录
                    moveChildrenRecords(oldWorkPath, newWorkPath + targetFileName + "/", newRecord.getId(), type, fileRecord.getId());
                }
                // 删除原记录
                tsFilesMapper.deleteById(fileRecord.getId());
            }


        } else {
            // 重命名模式：插入新记录
            TsFiles newRecord = new TsFiles();
            newRecord.setNodeId(fileRecord.getNodeId());
            newRecord.setTaskId(fileRecord.getTaskId());
            newRecord.setIsFile(fileRecord.getIsFile());
            newRecord.setParentId(parentId);
            newRecord.setFileName(targetFileName);
            newRecord.setWorkPath(newWorkPath);
            newRecord.setBackupPath(fileRecord.getBackupPath());
            newRecord.setFileSize(fileRecord.getFileSize());
            newRecord.setKeywords(fileRecord.getKeywords());
            newRecord.setDescription(fileRecord.getDescription());
            newRecord.setUploadTime(new Timestamp(System.currentTimeMillis())); // 上传时间
            newRecord.setUploader(fileRecord.getUploader()); // 上传人
            tsFilesMapper.insert(newRecord);
            // 删除原记录
            tsFilesMapper.deleteById(fileRecord.getId());
        }
    }

    /**
     * 递归移动子项记录（覆盖模式下先移动后删除）
     */
    private void moveChildrenRecords(String oldPrefix, String newPrefix, String parentId, String type, String oldparentId) {
        //查询原来的
        List<TsFiles> children = tsFilesMapper.selectList(
                new QueryWrapper<TsFiles>().eq("parent_id", oldparentId));

        for (TsFiles child : children) {
            // 构建子项新路径
            String childNewPath = newPrefix;

            // 处理子项记录
            if ("0".equals(type)) {
                // 覆盖模式：更新或插入
                TsFiles existingChild = tsFilesMapper.selectOne(
                        new QueryWrapper<TsFiles>().eq("work_path", childNewPath)
                                .eq("file_name", child.getFileName()));

                if (existingChild != null) {
                    existingChild.setParentId(parentId);
                    existingChild.setWorkPath(childNewPath);
                    existingChild.setUpdateTime(new Timestamp(System.currentTimeMillis()));
                    tsFilesMapper.updateById(existingChild);
                } else {
                    TsFiles newChild = new TsFiles();
                    newChild.setNodeId(child.getNodeId());
                    newChild.setTaskId(child.getTaskId());
                    newChild.setIsFile(child.getIsFile());
                    newChild.setParentId(parentId);
                    newChild.setFileName(child.getFileName());
                    newChild.setWorkPath(childNewPath);
                    newChild.setBackupPath(child.getBackupPath());
                    newChild.setFileSize(child.getFileSize());
                    newChild.setKeywords(child.getKeywords());
                    newChild.setDescription(child.getDescription());
                    newChild.setUploadTime(new Timestamp(System.currentTimeMillis()));
                    newChild.setUploader(child.getUploader());
                    tsFilesMapper.insert(newChild);
                    // 递归处理子文件夹
                    if ("FOLDER".equals(child.getIsFile())) {
                        moveChildrenRecords(oldPrefix, newPrefix + newChild.getFileName() + "/", newChild.getId(), type, child.getId());
                    }
                }
            }
        }
    }

    /**
     * 检查路径是否为空
     */
    private boolean isPathEmpty(Path path) throws IOException {
        if (Files.isDirectory(path)) {
            try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(path)) {
                return !dirStream.iterator().hasNext();
            }
        }
        return false;
    }


    /**********************************************************移动文件或者文件夹结束***********************************************************************************/


    /**********************************************************复制文件或者文件夹开始***********************************************************************************/


    @Override
    @Transactional(rollbackFor = Exception.class)
    public String copyFileFolder(MoveCopyFileFolderRequest reques) throws IOException {
        // 1. 获取存储根路径
        StorageSourceConfig config = getStorageConfig("filePath", "local");


        Path rootPath = Paths.get(config.getValue());

        String newPath = reques.getNewPath();
        String oldpaths = reques.getOldpaths();
        String parentId = reques.getParentId();
        String rename = reques.getRename();
        String type = reques.getType();
        String[] newFileNames = reques.getNewFileName().split(",");
        // 数组转集合
        List<String> newFileNameList = Arrays.asList(newFileNames);

        // 2. 构建目标父目录的物理路径
        Path targetParentPhysicalPath = buildPhysicalPath(newPath, rootPath);

        // 3. 循环处理所有选中的文件/文件夹
        for (String fileName : newFileNameList) {
            // 构建原文件物理路径
            Path sourcePath = buildPhysicalPath(oldpaths + "/" + fileName, rootPath);


            if (!Files.exists(sourcePath)) {
                throw new IOException("源文件不存在: " + sourcePath);
            }

            // ==== 新增关键校验 ====
            // 1. 禁止移动到自身
            if (sourcePath.equals(targetParentPhysicalPath)) {
                throw new IllegalArgumentException("不能复制到自己所在目录");
            }

            // 2. 禁止文件夹移动到子目录
            if (Files.isDirectory(sourcePath)) {
                if (targetParentPhysicalPath.startsWith(sourcePath)) {
                    throw new IllegalArgumentException("禁止将文件夹复制到自己内部");
                }
            }

            // 生成目标文件名（处理重命名和冲突）
            String targetFileName = generateTargetFileName(targetParentPhysicalPath, fileName, rename, type);
            Path targetPath = targetParentPhysicalPath.resolve(targetFileName);

            // 3. 再次检查自移动
            if (sourcePath.equals(targetPath)) {
                throw new IllegalArgumentException("不能复制到自己当前位置");
            }
            // 执行物理复制
            copyPhysicalFile(sourcePath, targetPath, type);

            // 5. 插入新记录到数据库
            insertDatabaseRecord(parentId, oldpaths, fileName, newPath, targetFileName, type);
        }
        return "复制成功";
    }


    //========================= 核心辅助方法 =========================//

    /**
     * 复制物理文件/文件夹（带递归）
     */
    private void copyPhysicalFile(Path source, Path target, String type) throws IOException {
        if (Files.isDirectory(source)) {
            Files.createDirectories(target);
            try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(source)) {
                for (Path child : dirStream) {
                    String childName = child.getFileName().toString();
                    copyPhysicalFile(child, target.resolve(childName), type);
                }
            }
        } else {
            if ("0".equals(type)) { // 覆盖模式
                Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
            } else { // 重命名模式
                if (Files.exists(target)) {
                    throw new IOException("目标文件已存在: " + target);
                }
                Files.copy(source, target);
            }
        }
    }

    /**
     * 插入数据库记录（主记录）
     */
    private void insertDatabaseRecord(String parentId, String oldpaths, String fileName,
                                      String newPath, String targetFileName, String type) {
        // 获取用户信息
        // 获取当前用户和时间
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = (LoginUser) authentication.getPrincipal();
        Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());
        //判断是不是覆盖  如果是覆盖  更新时间


        // 查询原记录
        TsFiles original = tsFilesMapper.selectOne(new QueryWrapper<TsFiles>()
                .eq("work_path", oldpaths)
                .eq("file_name", fileName)
        );
        //查询新纪录
        TsFiles neworiginal = tsFilesMapper.selectOne(new QueryWrapper<TsFiles>()
                .eq("work_path", newPath)
                .eq("file_name", targetFileName)
        );

        //如果是覆盖 并且通过新路径和名称可以查询到  那就更新时间
        if ("0".equals(type) && neworiginal != null) {

            neworiginal.setUpdateTime(currentTime);
            tsFilesMapper.updateById(neworiginal);
            if ("FOLDER".equals(original.getIsFile())) {
                copyChildrenRecords(
                        formatDbPath(oldpaths),
                        formatDbPath(newPath),
                        original.getId(),
                        neworiginal.getId(),
                        type
                );
            }

        } else {
            // 创建新记录
            TsFiles newRecord = new TsFiles();
            newRecord.setNodeId(original.getNodeId());
            newRecord.setTaskId(original.getTaskId());
            newRecord.setIsFile(original.getIsFile());
            newRecord.setParentId(parentId);
            newRecord.setFileName(targetFileName);
            newRecord.setWorkPath(formatDbPath(newPath));
            newRecord.setFileSize(original.getFileSize());
            newRecord.setKeywords(original.getKeywords());
            newRecord.setDescription(original.getDescription());
            newRecord.setUploadTime(currentTime);
            newRecord.setUploader(loginuser.getUsername());

            tsFilesMapper.insert(newRecord);
            // 递归处理子项
            if ("FOLDER".equals(original.getIsFile())) {
                copyChildrenRecords(
                        formatDbPath(oldpaths),
                        formatDbPath(newPath + newRecord.getFileName() + "/"),
                        original.getId(),
                        newRecord.getId(),
                        type
                );
            }
        }


    }

    /**
     * 递归复制子项数据库记录（关键优化点）
     */
    private void copyChildrenRecords(String oldPrefix, String newPrefix,
                                     String originalParentId, String newParentId, String type) {
        List<TsFiles> children = tsFilesMapper.selectList(
                new QueryWrapper<TsFiles>().eq("parent_id", originalParentId)
        );
        // 获取当前用户和时间
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = (LoginUser) authentication.getPrincipal();
        Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());

        for (TsFiles child : children) {
            // 处理当前子项
            TsFiles processedChild = processChildRecord(
                    child, newParentId, newPrefix, oldPrefix, type, loginuser, currentTime
            );

            // 递归处理子文件夹
            if ("FOLDER".equals(child.getIsFile())) {
                copyChildrenRecords(
                        oldPrefix,
                        newPrefix,
                        child.getId(),
                        processedChild.getId(), // 使用新记录的ID作为父ID
                        type
                );
            }
        }
    }

    /**
     * 处理单个子项记录
     */
    private TsFiles processChildRecord(TsFiles child, String newParentId, String newPrefix,
                                       String oldPrefix, String type, LoginUser user, Timestamp now) {
        if ("0".equals(type)) { // 覆盖模式
            TsFiles existing = tsFilesMapper.selectOne(
                    new QueryWrapper<TsFiles>()
                            .eq("work_path", buildNewWorkPath(child, oldPrefix, newPrefix))
                            .eq("file_name", child.getFileName())
            );
            if (existing != null) {
                updateExisting(existing, child, now, user);
                return existing;
            } else {
                return insertNewChild(child, newParentId, buildNewWorkPath(child, oldPrefix, newPrefix),
                        user, now, null);
            }
        } else { // 重命名模式
            return insertNewChild(child, newParentId, newPrefix,
                    user, now, child.getFileName());
        }
    }

    /**
     * 更新现有记录（覆盖模式）
     */
    private void updateExisting(TsFiles existing, TsFiles source, Timestamp time, LoginUser user) {
        existing.setFileSize(source.getFileSize());
        existing.setDescription(source.getDescription());
        existing.setUpdateTime(time);
        existing.setUploader(user.getUsername());
        tsFilesMapper.updateById(existing);
    }

    /**
     * 插入新子项记录
     */
    private TsFiles insertNewChild(TsFiles source, String parentId, String newWorkPath,
                                   LoginUser user, Timestamp time, String customName) {
        TsFiles newChild = new TsFiles();
        newChild.setNodeId(source.getNodeId());
        newChild.setTaskId(source.getTaskId());
        newChild.setIsFile(source.getIsFile());
        newChild.setParentId(parentId);
        newChild.setFileName(customName != null ? customName : source.getFileName());
        newChild.setWorkPath(newWorkPath);
        newChild.setFileSize(source.getFileSize());
        newChild.setKeywords(source.getKeywords());
        newChild.setDescription(source.getDescription());
        newChild.setUploadTime(time);
        newChild.setUploader(user.getUsername());
        tsFilesMapper.insert(newChild);
        return newChild;
    }

//========================= 工具方法 =========================//

    /**
     * 构建安全的新路径（精确替换前缀）
     */
    private String buildNewWorkPath(TsFiles child, String oldPrefix, String newPrefix) {
        // 确保旧前缀是父路径（如 /a/b/ 而不是 /a/b/file.txt）
        String childPath = child.getWorkPath();

        // 关键修复：严格匹配父路径前缀
        if (!childPath.startsWith(oldPrefix)) {
            throw new IllegalStateException("路径前缀不匹配: " + childPath);
        }

        // 计算子项相对路径（剥离旧前缀）
        String relativePath = childPath.substring(oldPrefix.length());

        // 拼接新路径（避免重复层级）
        return newPrefix + relativePath;
    }

    /**
     * 格式化数据库路径（统一以/结尾）
     */
    private String formatDbPath(String path) {
        if (path == null || path.isEmpty()) return "/";
        return path.endsWith("/") ? path : path + "/";
    }

    /**
     * 获取当前用户（封装重复逻辑）
     */
    private LoginUser getCurrentUser() {
        return (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }

    /**
     * 关键安全校验
     */
    private void validateCopySafety(Path source, Path targetParent) {
        // 禁止复制到自身目录
        if (source.equals(targetParent)) {
            throw new IllegalArgumentException("不能复制到自身目录");
        }

        // 禁止文件夹嵌套复制
        if (Files.isDirectory(source) && targetParent.startsWith(source)) {
            throw new IllegalArgumentException("禁止将文件夹复制到自身内部");
        }
    }


    /**********************************************************复制文件或者文件夹结束***********************************************************************************/

    /**********************************
     * 用途说明: 查询本地结构树
     * 参数说明  taskId 节点ID
     * 参数说明  nodeId 任务ID
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据
     ***********************************/
    @Override
    public DualTreeResponse listLocalTree(String taskId, String nodeId) {
        // 记录方法入参
        LOGGER.info("Starting to build dual trees for taskId={}, nodeId={}", taskId, nodeId);
        // 1. 批量查询所有相关节点
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        if (nodeId != null && !nodeId.isEmpty()) {
            queryWrapper.eq("node_id", nodeId);
        }
        if (taskId != null && !taskId.isEmpty()) {
            queryWrapper.eq("task_id", taskId);
        }
        queryWrapper.isNotNull("work_path").ne("work_path", "");
//        queryWrapper.eq("parent_id", "00");

        List<TsFiles> allNodes = tsFilesMapper.selectList(queryWrapper);

        // 2. 双树构建容器
        List<TreeDTO> localTrees = new ArrayList<>();
        if (allNodes.isEmpty()) {
            return new DualTreeResponse(localTrees, null);
        }
        // 3. 构建内存索引提升查询效率
        Map<String, List<TsFiles>> parentChildrenMap = allNodes.stream()
                .collect(Collectors.groupingBy(TsFiles::getParentId));
        LOGGER.debug("使用｛｝个条目构建父子映射", parentChildrenMap.size());

        // 4. 从顶级节点（parentId为00）开始构建树
        List<TsFiles> rootNodes = parentChildrenMap.get("00");
        if (rootNodes == null || rootNodes.isEmpty()) {
            LOGGER.warn("找不到的根节点 taskId={}, nodeId={}", taskId, nodeId);
            return new DualTreeResponse(localTrees, null);
        }

        if (rootNodes != null) {
            for (TsFiles rootNode : rootNodes) {
                // 构建本地树
                TreeDTO localTree = buildTree(rootNode, parentChildrenMap, true);
                if (localTree != null) {
                    localTrees.add(localTree);
                    LOGGER.debug("添加了本地树节点: {}", localTree.getId());
                }
            }
        }
        LOGGER.info("Tree construction completed. Local nodes: {}", localTrees.size());
        return new DualTreeResponse(localTrees, null);
    }


    /**********************************
     * 用途说明: 查询备份空间结构树
     * 参数说明  taskId 节点ID
     * 参数说明  nodeId 任务ID
     * 参数说明  id     文件夹ID
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据
     ***********************************/
    @Override
    public DualTreeResponse listBackupTree(String taskId, String nodeId) {
        // 记录方法入参
        LOGGER.info("Starting to build dual trees for taskId={}, nodeId={}", taskId, nodeId);
        // 1. 批量查询所有相关节点
        QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
        if (nodeId != null && !nodeId.isEmpty()) {
            queryWrapper.eq("node_id", nodeId);
        }
        if (taskId != null && !taskId.isEmpty()) {
            queryWrapper.eq("task_id", taskId);
        }

        queryWrapper.isNotNull("backup_path").ne("backup_path", "");
//        queryWrapper.eq("parent_id", "00");
        List<TsFiles> allNodes = tsFilesMapper.selectList(queryWrapper);
        // 2. 双树构建容器
        List<TreeDTO> minioTrees = new ArrayList<>();
        // 3. 构建内存索引提升查询效率
        Map<String, List<TsFiles>> parentChildrenMap = allNodes.stream()
                .collect(Collectors.groupingBy(TsFiles::getParentId));
        LOGGER.debug("使用｛｝个条目构建父子映射", parentChildrenMap.size());
        if (allNodes.isEmpty()) {
            return new DualTreeResponse(null, minioTrees);
        }

        // 4. 从顶级节点（parentId为00）开始构建树
        List<TsFiles> rootNodes = parentChildrenMap.get("00");
        if (rootNodes == null || rootNodes.isEmpty()) {
            LOGGER.warn("找不到的根节点 taskId={}, nodeId={}", taskId, nodeId);
            return new DualTreeResponse(null, minioTrees);
        }

        if (rootNodes != null) {
            for (TsFiles rootNode : rootNodes) {
                // 构建本地树
                // 构建Minio树
                TreeDTO minioTree = buildTree(rootNode, parentChildrenMap, false);
                if (minioTree != null) {
                    minioTrees.add(minioTree);
                    LOGGER.debug("添加了MINIO树节点: {}", minioTree.getId());
                }
            }
        }
        LOGGER.info("Tree construction completed. MinIO nodes: {}", minioTrees.size());
        return new DualTreeResponse(null, minioTrees);
    }

    /**********************************
     * 用途说明: 获取文件url
     * 参数说明  id     文件夹ID
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回文件信息
     ***********************************/
    @Override
    public FileItemResult obtainUrl(String id, String type) {
        FileItemResult fileItemResult = new FileItemResult();
        TsFiles tsFiles = tsFilesMapper.selectById(id);
        String fileNameData = tsFiles.getFileName();
        String path = "";
        if ("local".equals(type)) {
            String workPath = tsFiles.getWorkPath();
            path = workPath + fileNameData;
        } else {
            String backupPath = tsFiles.getBackupPath();
            path = backupPath + fileNameData;
        }

        //准备获取文件的信息
        AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(type);
        fileItemResult = fileService.getFileItem(path);
        return fileItemResult;
    }


    /**
     * 递归构建树形结构（内存操作，不再查询数据库）
     *
     * @param current           当前节点
     * @param parentChildrenMap 父子关系映射表
     * @param isLocal           是否是本地树
     * @return 有效的树节点（路径不为空）
     */
    private TreeDTO buildTree(TsFiles current, Map<String, List<TsFiles>> parentChildrenMap, boolean isLocal) {
        // 1. 转换为DTO并检查路径有效性
        TreeDTO dto = convertToDTOs(current, isLocal);
        dto.setType(current.getIsFile());
        if (dto.getPath() == null || dto.getPath().trim().isEmpty()) {
            LOGGER.warn("由于路径为空，跳过节点｛｝", current.getId());
            return null;
        }

        // 2. 查找子节点（从内存映射中获取）
        List<TsFiles> children = parentChildrenMap.get(current.getId());
        if (children != null) {
            for (TsFiles child : children) {
                TreeDTO childDto = buildTree(child, parentChildrenMap, isLocal);
                if (childDto != null) {
                    dto.getChildren().add(childDto);
                }
            }
        }

        return dto;
    }

    /**
     * 将 FileNode 转换为 TreeDTO
     *
     * @param node    当前节点
     * @param isLocal 是否是本地树（true: workPath, false: backupPath）
     * @return 转换后的 DTO
     */
    private TreeDTO convertToDTOs(TsFiles node, boolean isLocal) {

        TreeDTO dto = new TreeDTO();
        // 复制公共字段
        dto.setId(node.getId());
        dto.setIsFile(node.getIsFile());
        dto.setParentId(node.getParentId());
        dto.setFileName(node.getFileName());
        dto.setFileSize(node.getFileSize());
        dto.setUploadTime(node.getUploadTime());
        dto.setUpdateTime(node.getUpdateTime());
        //查询本地
        if (isLocal) {
            String workPath = node.getWorkPath();
            if (StringUtils.isEmpty(workPath)) {
                dto.setPath(null);
                return dto;
            }
        } else {
            String backupPath = node.getBackupPath();
            if (backupPath == null || StringUtils.isEmpty(backupPath)) {
                dto.setPath(null);
                return dto;
            }
        }
        // 设置路径字段
//        if (isLocal) {
//            String ProcessingPath = processingPath(node.getWorkPath(), node.getNodeId());
//            dto.setPath(ProcessingPath);
//        } else {
//            String ProcessingPath = processingPath(node.getBackupPath(), node.getNodeId());
//            dto.setPath(ProcessingPath);
//        }
        // 设置路径字段
        if (isLocal) {
            String ProcessingPath = node.getWorkPath();
            dto.setPath(ProcessingPath);
        } else {
            String ProcessingPath = node.getBackupPath();
            dto.setPath(ProcessingPath);
        }

        dto.setUrl(null);
        dto.setType(null);

        return dto;
    }

    public String processingPath(String Path, String nodeId) {
        String newWorkPath = "";
        if (Path == null || nodeId == null) {
            return newWorkPath;
        }
        // 获取原始路径和 nodeId
        String workPath1 = Path;
        // 拼接需要去掉的部分
        String toRemove = File.separator + nodeId;
        // 将路径分隔符统一为 "/"
        String standardizedWorkPath = workPath1.replace("\\", "/");
        String standardizedToRemove = toRemove.replace("\\", "/");
        // 检查 standardizedWorkPath 是否包含 standardizedToRemove
        if (standardizedWorkPath.contains(standardizedToRemove)) {
            // 去掉目标部分
            newWorkPath = standardizedWorkPath.replace(standardizedToRemove, "");
            // 确保路径最后保留一个 '/'
            if (!newWorkPath.endsWith("/")) {
                newWorkPath += "/";
            }
        }
        return newWorkPath;
    }


    /*******************************************读取文件内容经纬度**************************************************************/
    /**********************************
     * 用途说明: 实时获取轨迹数据
     * 参数说明  id 文件的ID
     * 参数说明  samTimes 时间
     * 参数说明  token SSE连接的token
     * 返回值说明: com.yfd.platform.config.ResponseResult
     ***********************************/
    @Override
    public void batchSendNaviOutDataJob(String id, int samTimes, String token) {
        if (currentTaskFuture != null && !currentTaskFuture.isDone()) {
            currentTaskFuture.cancel(true); // 中断之前的任务
        }
        currentTaskFuture = executorService.submit(() -> {
            TsFiles tsFiles = tsFilesMapper.selectById(id);
            StorageSourceConfig config = getStorageConfig("filePath", "local");


            Path filePath = Paths.get(config.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName());

            try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toFile()))) {
                reader.readLine(); // 跳过标题行

                // 不管 samTimes 如何，先读取第一行数据
                String firstLine = reader.readLine();
                if (firstLine != null) {
                    firstLine = firstLine.trim();
                    String[] values = firstLine.split("\t");
                    if (values.length >= 40) {
                        // 发送第一行数据
                        sendData(token, values, 1);
                    } else {
                        System.err.println("忽略不完整行: " + firstLine);
                    }
                }

                int step = samTimes * 200; // 计算行号间隔
                int lineCount = 1; // 从第一行开始

                while (!Thread.currentThread().isInterrupted()) {
                    lineCount += step; // 直接跳到下一个需要读取的行
                    String line = null;

                    // 读取指定行
                    for (int i = 0; i < step; i++) {
                        line = reader.readLine();
                        if (line == null) {
                            break; // 文件结束
                        }
                    }

                    if (line == null) {
                        break; // 文件结束
                    }

                    line = line.trim();
                    String[] values = line.split("\t");
                    if (values.length < 40) {
                        LOGGER.info("忽略不完整行: " + line);
                        continue;
                    }

                    // 发送数据
                    sendData(token, values, lineCount);

                    // 固定休眠 3 秒
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        // 如果休眠期间被中断，则退出循环
                        Thread.currentThread().interrupt();
                        break;
                    }
                }

                // 发送完成信号（如果需要）
                if (!Thread.currentThread().isInterrupted()) {
                    //  ServerSendEventServer.sendMessage(token, "COMPLETED");
                }

            } catch (Exception e) {
                // 捕获异常并记录日志
                LOGGER.error("任务执行失败: " + e.getMessage());
                e.printStackTrace();
            }
        });
    }


    // 封装发送数据的逻辑
    private void sendData(String token, String[] values, int lineCount) {
        if (Thread.currentThread().isInterrupted()) {
            return;
        }

        try {
            SimpleNaviData data = parseLine(values);
            String jsonData = JSONUtil.toJsonStr(data); // 假设 JSONUtil.toJsonStr 不会抛出 IOException
            ServerSendEventServer.sendMessageById(token, jsonData); // 假设 sendMessage 不会抛出 IOException
            LOGGER.info("Line " + lineCount + " sent at: " + System.currentTimeMillis());
        } catch (Exception e) {
            // 捕获所有可能的异常
            LOGGER.error("发送数据失败: " + e.getMessage());
            if (e.getCause() instanceof InterruptedException) {
                Thread.currentThread().interrupt(); // 重新设置中断状态
            }
        }
    }

    // 辅助方法：解析行数据
    private SimpleNaviData parseLine(String[] values) {
        SimpleNaviData data = new SimpleNaviData();
        data.setUtcTime(getValueSafely(values, 8, "0.0")); // UTC_TIME（索引8）
        data.setLat(getValueSafely(values, 17, "0.0"));    // LAT（索引17）
        data.setLon(getValueSafely(values, 18, "0.0"));    // LON（索引18）
        data.setAlt(getValueSafely(values, 19, "0.0"));    // ALT（索引19）
        return data;
    }

    // 安全获取数组值的方法
    private String getValueSafely(String[] values, int index, String defaultValue) {
        return (index < values.length) ? values[index] : defaultValue;
    }


    /**
     * 查询文件内容接口
     *
     * @param id 文件的id
     * @return 文件内容的纯文本（UTF-8 编码）
     */
    @Override
    public String readFileContent(String id) throws IOException {

        StorageSourceConfig config = getStorageConfig("filePath", "local");

        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 = getStorageConfig("filePath", "local");

        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       文件写入失败时抛出
     */
    @Override
    public void saveFileContent(String id, String content) throws SecurityException, IOException {

        StorageSourceConfig config = getStorageConfig("filePath", "local");
        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);
        }
    }

    /**********************************
     * 用途说明: 批量修改文件中多行多列的内容
     * 参数说明  request 要修改的文件信息
     * 返回值说明: com.yfd.platform.config.ResponseResult操作结果
     ***********************************/
    @Override
    public void batchUpdateFile(String id, List<ModifyCommand> modifications) throws IOException {
        StorageSourceConfig config = getStorageConfig("filePath", "local");
        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<String> 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<String> lines, List<ModifyCommand> 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);
        }
    }

    /**********************************
     * 用途说明: 删除整个节点以及数据库的数据-物理删除
     * 参数说明  nodeId 节点ID
     * 参数说明  taskId 任务ID
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败
     ***********************************/
    @Override
    @Transactional(rollbackFor = Exception.class)// 添加事务注解，遇到异常时回滚
    public Boolean deleteTsFilesByNodeId(String nodeId, String taskId) {
        List<StorageSource> storageSourceList = storageSourceMapper.selectList(new QueryWrapper<StorageSource>());
        int deleteSuccessCount = 0, deleteFailCount = 0;
        Boolean value = false;

        for (StorageSource storageSource : storageSourceList) {
            //如果是文件夹
            List<BatchDeleteRequest.DeleteItem> deleteItemList = new ArrayList<>();
            BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem();
            deleteItemData.setName(nodeId);
            deleteItemData.setPassword("");
            deleteItemData.setPath(File.separator);

            deleteItemData.setType(FileTypeEnum.FOLDER);
            deleteItemList.add(deleteItemData);
            //首先通过ID集合查询所有的内容  然后放到List<DeleteItem> deleteItems里面  放好以后删除数据库  然后删除minio
            BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest();
            batchDeleteRequest.setDeleteItems(deleteItemList);
            batchDeleteRequest.setStorageKey(storageSource.getKey());

            AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey());
            List<BatchDeleteRequest.DeleteItem> deleteItems = batchDeleteRequest.getDeleteItems();

            for (BatchDeleteRequest.DeleteItem deleteItem : deleteItems) {

                boolean flag = false;
                try {
                    if (deleteItem.getType() == FileTypeEnum.FILE) {
                        flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName());
                    } else if (deleteItem.getType() == FileTypeEnum.FOLDER) {
                        flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName());
                    }
                    if (flag) {
                        deleteSuccessCount++;
                    } else {
                        deleteFailCount++;
                    }
                } catch (Exception e) {
                    LOGGER.error("删除文件/文件夹失败, 文件路径: {}, 文件名称: {}", deleteItem.getPath(), deleteItem.getName(), e);
                    deleteFailCount++;
                }
            }
        }


        //如果是1 说明成功删除
        if (deleteSuccessCount >= 1) {
            //删除数据库
            QueryWrapper<TsFiles> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("node_id", nodeId);
            queryWrapper.eq("task_id", taskId);
            int valueDelete = tsFilesMapper.delete(queryWrapper);
            if (valueDelete > 0) {
                value = true;
                LOGGER.info("物理删除表结构成功");
            } else {
                value = false;
                System.out.println("物理删除表结构");
            }
        }
        return value;

    }

    /**********************************
     * 用途说明: 停止获取轨迹数据
     * 参数说明  token SSE连接的token
     * 返回值说明: com.yfd.platform.config.ResponseResult
     ***********************************/
    @Override
    public void stopSimpleNavi(String token) {
        if (currentTaskFuture != null && !currentTaskFuture.isDone()) {
            currentTaskFuture.cancel(true); // 中断之前的任务
        }
        ServerSendEventServer.removeUser(token);
    }


}



