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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.io.FileUtil;
import com.amazonaws.util.IOUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yfd.platform.component.TaskStatusHolder;
import com.yfd.platform.component.WebSocketServer;
import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.exception.file.InvalidStorageSourceException;
import com.yfd.platform.modules.config.model.request.FileListRequest;

import com.yfd.platform.modules.specialDocument.domain.Files;
import com.yfd.platform.modules.specialDocument.domain.Nodes;
import com.yfd.platform.modules.specialDocument.domain.Project;
import com.yfd.platform.modules.specialDocument.mapper.FilesMapper;
import com.yfd.platform.modules.specialDocument.mapper.NodesMapper;
import com.yfd.platform.modules.specialDocument.mapper.ProjectMapper;
import com.yfd.platform.modules.specialDocument.service.INodesService;
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.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.RenameFolderRequest;
import com.yfd.platform.modules.storage.model.result.FileItemResult;
import com.yfd.platform.modules.storage.service.StorageSourceService;
import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService;
import com.yfd.platform.system.domain.LoginUser;
import com.yfd.platform.utils.StringUtils;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;


/**
 * <p>
 * 专项文档节点表 服务实现类
 * </p>
 *
 * @author LiMengNan
 * @since 2025-01-20
 */
@Service
public class NodesServiceImpl extends ServiceImpl<NodesMapper, Nodes> implements INodesService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);


    //专项文档节点表 Mapper
    @Resource
    private NodesMapper nodesMapper;

    //专项文档表 Mapper
    @Resource
    private FilesMapper filesMapper;

    @Resource
    private StorageSourceConfigMapper storageSourceConfigMapper;

    @Resource
    private StorageSourceContext storageSourceContext;

    @Resource
    private StorageSourceService storageSourceService;

    @Autowired
    private TaskStatusHolder taskStatusHolder;

    //专项项目表Mapper
    @Resource
    private ProjectMapper projectMapper;

    //顶级父节点  Top level parent node
    public static final String TOP_LEVEL_PARENT_NODE = "00";

    private static final String INITIAL_CODE = "00001";
    private static final int MAX_CODE_VALUE = 99999;

    /***********************************
     * 用途说明：获取专项文档节点 树形结构
     * 参数说明
     *  nodeName        节点名称
     *  projectId        所属项目ID
     * 返回值说明:  专项文档节点树形结构
     ***********************************/
    @Override
    public List<Map<String, Object>> getNodesTree(String nodeName, String projectId) {
        Project project = projectMapper.selectById(projectId);

        // 查询所有节点数据
        List<Map<String, Object>> allNodes = getAllNodes(projectId);
        // 查找所有根节点（parentId为"00"的节点）
        List<Map<String, Object>> rootNodes = findRootNodes(allNodes, projectId);
        // 如果未找到根节点，返回空列表
        if (rootNodes.isEmpty()) {
            return new ArrayList<>();
        }
        // 根节点的基本路径：/项目名称/
        String basePath = "/" + project.getProjectName() + "/";

        // 存储最终结果
        List<Map<String, Object>> result = new ArrayList<>();
        // 如果 nodeName 为空，返回所有根节点的完整树形结构
        if (StringUtils.isEmpty(nodeName)) {
            for (Map<String, Object> rootNode : rootNodes) {
                rootNode.put("path", basePath);
                result.addAll(buildFullTree(rootNode, allNodes));
            }
            return result;
        }
        // 否则，返回从根节点到目标节点的树形结构
        for (Map<String, Object> rootNode : rootNodes) {
            rootNode.put("path", basePath);
            List<Map<String, Object>> tree = buildTreeToTargetNode(rootNode, allNodes, nodeName);
            if (!tree.isEmpty()) {
                result.addAll(tree);
            }
        }
        // 返回结果
        return result;
    }

    /**
     * 构建完整的树形结构
     *
     * @param currentNode 当前节点
     * @param allNodes    所有节点数据
     * @return 返回当前节点的子树
     */
    private List<Map<String, Object>> buildFullTree(Map<String, Object> currentNode, List<Map<String, Object>> allNodes) {
        // 查找当前节点的所有子节点
        List<Map<String, Object>> children = findChildren(allNodes, currentNode.get("id").toString());

        // 为每个子节点设置路径：父路径 + 父节点名称 + "/"
        for (Map<String, Object> child : children) {
            String parentPath = (String) currentNode.get("path");
            String parentName = (String) currentNode.get("nodeName");
            child.put("path", parentPath + parentName + "/");
        }

        // 递归构建子树
        List<Map<String, Object>> tree = new ArrayList<>();
        for (Map<String, Object> child : children) {
            List<Map<String, Object>> childTree = buildFullTree(child, allNodes);
            if (!childTree.isEmpty()) {
                tree.addAll(childTree);
            }
        }
        // 将当前节点加入树中
        Map<String, Object> nodeWithChildren = new HashMap<>(currentNode);
        if (!tree.isEmpty()) {
            nodeWithChildren.put("children", tree);
        }
        return Collections.singletonList(nodeWithChildren);
    }

    /**
     * 构建从根节点到目标节点的树形结构
     *
     * @param currentNode 当前节点
     * @param allNodes    所有节点数据
     * @param nodeName    目标节点名称
     * @return 返回从根节点到目标节点的树形结构
     */
    private List<Map<String, Object>> buildTreeToTargetNode(Map<String, Object> currentNode, List<Map<String, Object>> allNodes, String nodeName) {

        List<Map<String, Object>> result = new ArrayList<>();

        // 查找当前节点的所有子节点
        List<Map<String, Object>> children = findChildren(allNodes, currentNode.get("id").toString());
        for (Map<String, Object> child : children) {
            String parentPath = (String) currentNode.get("path");
            String parentName = (String) currentNode.get("nodeName");
            child.put("path", parentPath + parentName + "/");
        }

        // 递归查找目标节点
        for (Map<String, Object> child : children) {
            List<Map<String, Object>> childTree = buildTreeToTargetNode(child, allNodes, nodeName);
            if (!childTree.isEmpty()) {
                // 如果找到目标节点，将当前节点加入树中，并将其作为子节点
                Map<String, Object> nodeWithChildren = new HashMap<>(currentNode);
                nodeWithChildren.put("children", childTree);
                result.add(nodeWithChildren);  // 将当前节点加入结果列表
            }
        }

        // 如果当前节点符合条件且没有被添加到result，则将其添加
        if (currentNode.get("nodeName") instanceof String && ((String) currentNode.get("nodeName")).contains(nodeName) && result.isEmpty()) {
            result.add(currentNode);  // 将当前节点添加到结果列表
        }

        // 返回包含所有符合条件的树结构的列表
        return result;
    }

    /**
     * 查找当前节点的所有子节点
     *
     * @param allNodes 所有节点数据
     * @param parentId 父节点ID
     * @return 返回所有子节点列表
     */
    private List<Map<String, Object>> findChildren(List<Map<String, Object>> allNodes, String parentId) {
        List<Map<String, Object>> children = new ArrayList<>();
        for (Map<String, Object> node : allNodes) {
            if (parentId.equals(node.get("parentId").toString())) {
                children.add(node);
            }
        }
        return children;
    }

    /**
     * 查找所有根节点（parentId为"00"的节点）
     *
     * @param allNodes  所有节点数据
     * @param projectId 项目ID
     * @return 返回所有根节点列表
     */
    private List<Map<String, Object>> findRootNodes(List<Map<String, Object>> allNodes, String projectId) {
        List<Map<String, Object>> rootNodes = new ArrayList<>();
        for (Map<String, Object> node : allNodes) {
            if ("00".equals(node.get("parentId").toString()) && projectId.equals(node.get("projectId").toString())) {
                rootNodes.add(node);
            }
        }
        return rootNodes;
    }

    /**
     * 查询所有节点数据
     *
     * @param projectId 项目ID（用于精确查询）
     * @return 返回所有节点列表
     */
    private List<Map<String, Object>> getAllNodes(String projectId) {
        // 创建查询条件
        QueryWrapper<Nodes> queryWrapper = new QueryWrapper<>();
        queryWrapper.select(
                "id as id",                      // 节点ID
                "node_name as nodeName",         // 节点名称
                "project_id as projectId",       // 项目ID
                "parent_id as parentId",         // 父节点ID
                "node_order as nodeOrder",       // 节点顺序
                "node_type as nodeType",         // 节点类型
                "create_time as createTime",     // 创建时间
                "creator"                        // 创建人
        );
        // 如果项目ID不为空，添加精确查询条件
        if (StringUtils.isNotEmpty(projectId)) {
            queryWrapper.eq("project_id", projectId);
        }
        // 按节点顺序升序排序
        queryWrapper.orderByAsc("node_order");
        // 查询所有符合条件的节点
        return nodesMapper.selectMaps(queryWrapper);
    }


    /**********************************
     * 用途说明: 增加专项文档节点
     * 参数说明  nodes 专项文档节点信息
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败
     ***********************************/
    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务注解，遇到异常时回滚
    public ResponseResult addNodes(Nodes nodes) {

        // 校验文件名是否包含非法字符
        String nodeName = nodes.getNodeName();
        if (containsInvalidCharacters(nodeName)) {
            return ResponseResult.error("文件名包含非法字符（<>:\"/\\|?*）！");
        }


        //获取当前登录用户
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = (LoginUser) authentication.getPrincipal();
        //创建人是当前登录人
        nodes.setCreator(loginuser.getUsername());

        //当前操作时间
        LocalDateTime now = LocalDateTime.now();
        // 转换为 Timestamp
        Timestamp currentTime = Timestamp.valueOf(now);
        nodes.setCreateTime(currentTime);

        //通过获取上级节点的条数 设置节点顺序
        QueryWrapper<Nodes> queryWrapperNodeOrder = new QueryWrapper<>();
        int orderno = this.count(queryWrapperNodeOrder.eq("parent_id", nodes.getParentId())) + 1;
        //判断节点名称是否存在
        LambdaQueryWrapper<Nodes> queryWrapper = new LambdaQueryWrapper<>();
        //名称
        queryWrapper.eq(Nodes::getNodeName, nodes.getNodeName());
        //父节点
        queryWrapper.eq(Nodes::getParentId, nodes.getParentId());
        //所属项目ID
        queryWrapper.eq(Nodes::getProjectId, nodes.getProjectId());
        int count = nodesMapper.selectCount(queryWrapper);
        // 大于0说明 区域名称重复
        if (count > 0) {
            return ResponseResult.error("节点名称已存在！");
        }
        //序号
        nodes.setNodeOrder(orderno);
        int valueAdded = nodesMapper.insert(nodes);
        if (valueAdded == 1) {
            List<String> pathNodes = new ArrayList<>();
            Nodes nodesData = nodesMapper.selectById(nodes.getParentId());
            // 从当前节点向上遍历，直到根节点
            while (nodesData != null) {
                pathNodes.add(nodesData.getNodeName());
                // 如果父节点是 "00"，说明已经到了根节点，停止遍历
                if ("00".equals(nodesData.getParentId())) {
                    break;
                }
                // 获取父节点  // 修正：从 nodesData 中获取 parentId
                nodesData = nodesMapper.selectById(nodesData.getParentId());
            }
            // 反转路径，使其从根节点到当前节点
            Collections.reverse(pathNodes);


            //新增节点的时候 创建文件夹
            NewFolderRequest newFolderRequest = new NewFolderRequest();
            newFolderRequest.setName(nodes.getNodeName());//新建的文件夹名称,示例值(/a/b/c)
            newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问，则支持请求密码,示例值(123456)
            newFolderRequest.setPath(nodes.getPath());//请求路径,示例值(/)
            newFolderRequest.setStorageKey("sdlocal");//存储源 key,示例值(local minio sdlocal)
            AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey());
            boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName());
            if (flag) {
                return ResponseResult.success();
            } else {
                LOGGER.error("节点新增成功，但是sdlocal创建文件失败");
                return ResponseResult.error();
            }

        } else {
            LOGGER.error("节点新增失败");
            return ResponseResult.error();
        }
    }

    /**********************************
     * 用途说明: 修改专项文档节点
     * 参数说明  nodes 专项文档节点信息
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败
     ***********************************/
    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务注解，遇到异常时回滚
    public ResponseResult updateNodes(Nodes nodes) {

        //新的节点名称
        String nodeName = nodes.getNodeName();
        if (containsInvalidCharacters(nodeName)) {
            return ResponseResult.error("文件名包含非法字符（<>:\"/\\|?*）！");
        }

        //查询没改之前的节点名称
        Nodes nodesold = nodesMapper.selectById(nodes.getId());
        //老的节点名称
        String nodeNameOld = null;
        if (ObjUtil.isNotEmpty(nodesold)) {
            nodeNameOld = nodesold.getNodeName();
        }

        //获取专项文档项目
        Project project = projectMapper.selectById(nodes.getProjectId());

        //获取当前登录用户
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = (LoginUser) authentication.getPrincipal();
        //创建人是当前登录人
        nodes.setCreator(loginuser.getUsername());

        //判断节点名称是否存在
        LambdaQueryWrapper<Nodes> queryWrapper = new LambdaQueryWrapper<>();
        //名称
        queryWrapper.eq(Nodes::getNodeName, nodes.getNodeName());
        //父节点
        queryWrapper.eq(Nodes::getParentId, nodes.getParentId());
        //所属项目ID
        queryWrapper.eq(Nodes::getProjectId, nodes.getProjectId());
        int count = nodesMapper.selectCount(queryWrapper);
        // 大于0说明 区域名称重复
        if (count > 0) {
            return ResponseResult.error("节点名称已存在！");
        }
        int valueUpdate = nodesMapper.updateById(nodes);

        // 4. 递归获取所有子节点ID（包含当前节点）
        List<String> affectedNodeIds = getAllChildNodeIds(nodes.getId());

        // 5. 更新相关文件路径
        updateFilePaths(affectedNodeIds, nodes.getProjectId(), nodeNameOld, nodeName);

        if (valueUpdate == 1) {
            List<String> pathNodes = new ArrayList<>();
            Nodes nodesData = nodesMapper.selectById(nodes.getParentId());
            // 从当前节点向上遍历，直到根节点
            while (nodesData != null) {
                pathNodes.add(nodesData.getNodeName());
                // 如果父节点是 "00"，说明已经到了根节点，停止遍历
                if ("00".equals(nodesData.getParentId())) {
                    break;
                }
                // 获取父节点
                nodesData = nodesMapper.selectById(nodesData.getParentId()); // 修正：从 nodesData 中获取 parentId
            }
            // 反转路径，使其从根节点到当前节点
            Collections.reverse(pathNodes);
            String path = String.join("/", pathNodes);

            //修改文件名称
            RenameFolderRequest renameFolderRequest = new RenameFolderRequest();
            renameFolderRequest.setName(nodeNameOld);//重命名的原文件夹名称,示例值(movie)
            renameFolderRequest.setNewName(nodeName);//	重命名后的文件名称,示例值(music)
            renameFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问，则支持请求密码,示例值(123456)
            renameFolderRequest.setPath(nodes.getPath());//请求路径,示例值(/)
            renameFolderRequest.setStorageKey("sdlocal");//存储源 key,示例值(local minio sdlocal)
            AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(renameFolderRequest.getStorageKey());
            boolean flag = fileService.renameFolder(renameFolderRequest.getPath(), renameFolderRequest.getName(), renameFolderRequest.getNewName());
            if (flag) {
                return ResponseResult.success("重命名成功");
            } else {
                LOGGER.error("节点修改成功，但是sdlocal修改文件名失败");
                return ResponseResult.error("重命名失败");
            }
        } else {
            LOGGER.error("节点修改失败");
            return ResponseResult.error();
        }
    }

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

    /**
     * 更新文件路径中的节点名称
     */
    private void updateFilePaths(List<String> nodeIds, String projectId, String oldName, String newName) {
        // 查询需要更新的文件记录
        List<Files> filesToUpdate = filesMapper.selectList(
                new QueryWrapper<Files>()
                        .in("node_id", nodeIds)
                        .eq("project_id", projectId)
        );

        // 构建路径替换规则
        String pathPattern = "/" + oldName;  // 正确转义斜杠
        String replacement = "/" + newName;  // 保留斜杠或结尾不变

        // 批量更新文件路径
        filesToUpdate.forEach(file -> {
            String newPath = file.getFilePath().replace(pathPattern, replacement);
            file.setFilePath(newPath);
        });
        // 批量更新数据库
        if (!filesToUpdate.isEmpty()) {
            for (Files files : filesToUpdate) {
                filesMapper.updateById(files);
            }
        }
    }


    /**
     * 递归获取所有子节点ID（包含当前节点）
     */
    private List<String> getAllChildNodeIds(String nodeId) {
        List<String> nodeIds = new ArrayList<>();
        nodeIds.add(nodeId);

        // 查询直接子节点
        List<Nodes> directChildren = nodesMapper.selectList(
                new QueryWrapper<Nodes>().eq("parent_id", nodeId));

        // 递归获取子节点的子节点
        for (Nodes child : directChildren) {
            nodeIds.addAll(getAllChildNodeIds(child.getId()));
        }

        return nodeIds;
    }


    /**********************************
     * 用途说明: 根据ID删除专项文档节点
     * 参数说明  id 专项文档节点ID
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败
     ***********************************/
    @Override
    @Transactional(rollbackFor = Exception.class) // 启用事务
    public boolean deleteNodesById(String id, String path) {
        Boolean value = false;


        // 根据ID 查询当前数据
        Nodes nodes = nodesMapper.selectById(id);

        if (nodes == null) {
            return false; // 节点不存在
        }
        //递归获取当前节点和它下面的所有节点
        List<Nodes> nodesList = selectChildrenNodes(id, nodes.getProjectId());
        // 提取所有节点的 ID
        List<String> nodeIds = nodesList.stream()
                .map(Nodes::getId)  // 获取每个节点的 ID
                .collect(Collectors.toList());

        // 执行批量删除节点操作
        if (!nodeIds.isEmpty()) {
            QueryWrapper<Nodes> deleteWrapper = new QueryWrapper<>();
            deleteWrapper.in("id", nodeIds);  // 根据节点 ID 批量删除
            nodesMapper.delete(deleteWrapper);
        }
        //执行批量删除文件操作
        if (!nodeIds.isEmpty()) {
            QueryWrapper<Files> deleteWrapper = new QueryWrapper<>();
            deleteWrapper.in("node_id", nodeIds);  // 根据节点 ID 批量删除
            deleteWrapper.eq("project_id",nodes.getProjectId());
            filesMapper.delete(deleteWrapper);
        }

        // 删除 sdlocal 中的文件夹
        List<BatchDeleteRequest.DeleteItem> deleteItemList = new ArrayList<>();
        BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem();
        deleteItemData.setName(nodes.getNodeName());
        deleteItemData.setPassword("");
        deleteItemData.setPath(path);
        deleteItemData.setType(FileTypeEnum.FOLDER);
        deleteItemList.add(deleteItemData);

        BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest();
        batchDeleteRequest.setDeleteItems(deleteItemList);
        batchDeleteRequest.setStorageKey("sdlocal");
        AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey());

        int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItemList);
        for (BatchDeleteRequest.DeleteItem deleteItem : deleteItemList) {
            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 (deleteSuccessCount >= 1) {
            value = true;
        } else {
            value = false;
        }

        return value;
    }

    /**
     * 递归删除子节点
     * parentId 父节点ID
     * projectId 项目ID
     */
    private List<Nodes> selectChildrenNodes(String parentId, String projectId) {
        List<Nodes> nodesList  = new ArrayList<>();
        Nodes nodes = nodesMapper.selectById(parentId);
        // 查询当前节点的所有子节点
        QueryWrapper<Nodes> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", parentId);
        queryWrapper.eq("project_id", projectId);
        List<Nodes> children = nodesMapper.selectList(queryWrapper);
        // 将子节点添加到 nodesList 中
        nodesList.addAll(children);
        nodesList.add(nodes);

        // 递归删除每个子节点
        for (Nodes child : children) {
            // 递归查询子节点的子节点
            List<Nodes> childNodes = selectChildrenNodes(child.getId(), projectId);
            // 将子节点的所有子节点添加到 nodesList 中
            nodesList.addAll(childNodes);
        }
        return nodesList;
    }

    /**
     * 递归删除子节点
     */
    private void deleteChildren(String parentId, String projectId) {
        // 查询当前节点的所有子节点
        QueryWrapper<Nodes> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", parentId);
        queryWrapper.eq("project_id", projectId);
        List<Nodes> children = nodesMapper.selectList(queryWrapper);

        // 递归删除每个子节点
        for (Nodes child : children) {
            // 删除子节点下的文件
            QueryWrapper<Files> queryWrapper1 = new QueryWrapper<>();
            queryWrapper1.eq("node_id", child.getId()); // 使用子节点的 ID
            queryWrapper1.eq("project_id", projectId);
            filesMapper.delete(queryWrapper1);

            // 递归删除子节点的子节点
            deleteChildren(child.getId(), projectId);

            // 删除当前子节点
            nodesMapper.deleteById(child.getId());
        }
    }


    /**********************************
     * 用途说明: 专项文档管理扫描接口
     * 参数说明  id 所属项目ID
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
     ***********************************/
    @Override
    @Async("asyncExecutor")
    public void specialScanByIdAsync(String id) throws Exception {
        try {
            // 执行扫描并且插入数据库
            this.specialScanById(id);
        } finally {
            // 生成唯一Key
            String asyncKey = taskStatusHolder.specialGenerateKey(id);
            // 无论成功失败都标记完成
            taskStatusHolder.finishTask(asyncKey);
            WebSocketServer.sendMessageTo("专项文档扫描任务处理完成!", "projectId_"+id);

        }
    }


    /**********************************
     * 用途说明: 专项文档管理扫描接口实际接口
     * 参数说明  id 所属项目ID
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
     ***********************************/
    public String specialScanById(String id) throws Exception {

        //查询项目信息
        Project project = projectMapper.selectById(id);


        // 查询本地文件路径根目录（如 E:\yun）
        QueryWrapper<StorageSourceConfig> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "filePath");
        queryWrapper.eq("type", "sdlocal");
        StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper);

        //获取文件列表
        String absolutePath = "/" + project.getProjectName() + "/";
        //获取当前登录用户         上传人是当前登录人
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = null;
        if (authentication != null) {
            loginuser = (LoginUser) authentication.getPrincipal();
        }
        //登录人
        String uploader = null;
        if (loginuser != null) {
            uploader = loginuser.getUsername();
        }
        // 设置当前时间
        LocalDateTime now = LocalDateTime.now();
        // 转换为 Timestamp
        Timestamp currentTime = Timestamp.valueOf(now);
        // 存储所有目录和文件的列表
        List<Nodes> nodesToCreate = new ArrayList<>();
        List<Files> filesToCreate = new ArrayList<>();

        String path = storageSourceConfig.getValue() + "/" + absolutePath;
        File projectDir = new File(path);
        // 获取所有子目录
        List<File> allSubDirs = new ArrayList<>();
        getAllSubDirectories(projectDir, allSubDirs);
        String storageSourceConfigPath = storageSourceConfig.getValue().replace("\\", "/");
        // 批量处理子目录
        long startBuildNodes = System.currentTimeMillis();
        for (File subDir : allSubDirs) {
            String normalizedSubDirPath = subDir.getAbsolutePath().replace("\\", "/");
            // 获取上一级目录
            String parentPath = normalizedSubDirPath.substring(0, normalizedSubDirPath.lastIndexOf("/"));
            // 去掉 D:/yun 部分
            String finalPath = parentPath.replace(storageSourceConfigPath, "");
            Nodes nodes = new Nodes();
            nodes.setId(IdUtil.fastSimpleUUID());
            nodes.setProjectId(id);
            nodes.setParentId("00");
            nodes.setNodeOrder(1);
            // 1. 处理空路径，确保默认值
            String filePath = ensurePathFormat(finalPath);
            // 2. 路径标准化处理：统一替换所有分隔符为 "/"，并合并连续分隔符
            String normalizedPath = filePath
                    .replaceAll("[/\\\\]+", "/")  // 将 "\" 或混合分隔符统一为 "/"
                    .replaceAll("/+$", "");       // 移除末尾的 "/"（可选，根据需求）
            // 3. 分割路径段
            String[] pathSegments = normalizedPath.split("/");
            // 4. 计算有效层级深度
            long depth = Arrays.stream(pathSegments)
                    .filter(s -> !s.isEmpty())
                    .count();
            // 5. 生成 nodeType（两位数字）
            String nodeType = String.format("%02d", depth);
            nodes.setNodeType(nodeType);
            nodes.setNodeName(subDir.getName());
            nodes.setCustom3(ensurePathFormat(finalPath));
            nodes.setCreateTime(currentTime);
            nodesToCreate.add(nodes);


        }
        LOGGER.info("[构建节点列表] 耗时 {} ms | 待处理数量: {} 条",
                System.currentTimeMillis() - startBuildNodes,
                nodesToCreate.size());

        //todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件，递归增加
        if (!projectDir.exists() || projectDir.list().length == 0) {
            throw new Exception("该项目目录不存在或没有项目文档，请先建立项目目录和文档。");
        }
        // 批量插入节点表（忽略重复）
        if (!nodesToCreate.isEmpty()) {
            long startBatchInsert = System.currentTimeMillis();
            int affectedRows = nodesMapper.batchInsertIgnore(nodesToCreate);
            LOGGER.info("[批量插入节点表] 耗时 {} ms | 实际新增数量: {} 条",
                    System.currentTimeMillis() - startBatchInsert,
                    affectedRows);
        }

        //批量插入节点以后开始修改节点表中的父节点ID
        // 记录开始时间
        long startTime = System.currentTimeMillis();
        // 执行修改操作
        int affectedLevelRows = nodesMapper.updateParentIdByPathHierarchy();
         // 记录结束时间
        long endTime = System.currentTimeMillis();
         // 计算耗时
        long costTime = endTime - startTime;
        // 打印日志
        LOGGER.info("节点表中的parent_id更新完成，影响 {} 行，总耗时 {} 毫秒", affectedLevelRows, costTime);

        //接下来就是新增文件表   新增成功以后  通过路径获取节点表中的 custom3+路径 就是node ID
        // 获取所有文件
        List<File> allFiles = new ArrayList<>();
        getAllFiles(projectDir, allFiles);
        System.out.println("allFiles=" + allFiles.size());

        long startBuildFiles = System.currentTimeMillis();
        // 批量处理文件
        for (File file : allFiles) {
            String normalizedFilesPath = file.getAbsolutePath().replace("\\", "/");
            // 获取上一级目录
            String parentPath = normalizedFilesPath.substring(0, normalizedFilesPath.lastIndexOf("/"));
            // 去掉 D:/yun 部分
            String finalPath = parentPath.replace(storageSourceConfigPath, "");
            Files files = new Files();
            files.setId(IdUtil.fastSimpleUUID());
            files.setProjectId(id);
            files.setNodeId("00");
            files.setFileName(file.getName());
            files.setFilePath(ensurePathFormat(finalPath));
            files.setFileSize(String.valueOf(file.length()));
            files.setUploadTime(currentTime);
            files.setUploader(uploader);
            filesToCreate.add(files);
        }
        LOGGER.info("[构建文件列表] 耗时 {} ms | 待处理数量: {} 条",
                System.currentTimeMillis() - startBuildFiles,
                filesToCreate.size());
        // 批量插入文件表（忽略重复）
        if (!filesToCreate.isEmpty()) {
            long startBatchInsertFiles = System.currentTimeMillis();
            int affectedRowsFiles = filesMapper.batchInsertFiles(filesToCreate);
            LOGGER.info("[批量插入文件表] 耗时 {} ms | 实际新增数量: {} 条",
                    System.currentTimeMillis() - startBatchInsertFiles,
                    affectedRowsFiles);
        }
        //修改文件表中的nodeId

        // 记录开始时间
        long startTimeFiles = System.currentTimeMillis();
         // 执行更新操作
        int affectedLevelFilesRows = filesMapper.updateNodeIdByPathHierarchy(id);
         // 记录结束时间
        long endTimeFiles = System.currentTimeMillis();
        // 计算耗时
        long costTimeFiles = endTimeFiles - startTimeFiles;
        // 打印日志
        LOGGER.info("文件表中的节点ID更新完成，影响 {} 行，总耗时 {} 毫秒", affectedLevelFilesRows, costTimeFiles);


        return "扫描完成";
    }

    private void getAllSubDirectories(File dir, List<File> subDirs) {
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    subDirs.add(file);
                    getAllSubDirectories(file, subDirs);
                }
            }
        }
    }


    private void getAllFiles(File dir, List<File> files) {
        File[] fileList = dir.listFiles();
        if (fileList != null) {
            for (File file : fileList) {
                if (file.isFile()) {
                    files.add(file);
                } else if (file.isDirectory()) {
                    getAllFiles(file, files);
                }
            }
        }
    }


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

    /**
     * 第一层下面应该只有文件夹
     *
     * @param fileItemList 项目下面第一层级的所有数据
     * @param projectId    项目ID
     * @throws Exception
     */
    public void firstLayerData(List<FileItemResult> fileItemList, String projectId) throws Exception {

        int index = 1;
        for (FileItemResult item : fileItemList) {
            //思路就是 如果是文件夹  就查询一下 没有就新增， 新的的时候递归往下走
            if (item.getType() == FileTypeEnum.FOLDER) {
                //先查询有没有 如果没有就新增
                LambdaQueryWrapper<Nodes> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(Nodes::getProjectId, projectId);
                queryWrapper.eq(Nodes::getParentId, TOP_LEVEL_PARENT_NODE);
                queryWrapper.eq(Nodes::getNodeName, item.getName());
//                queryWrapper.eq(Nodes::getNodeOrder, obtainNodeType(index));
                Nodes nodeData = nodesMapper.selectOne(queryWrapper);
                //如果没有 新增 并且递归
                if (nodeData == null) {
                    Nodes node = saveNodes(projectId, TOP_LEVEL_PARENT_NODE, item.getName(), obtainNodeType(index));
                    otherLevelsData(projectId, node.getId(), item.getName(), item.getPath(), index);
                } else {
                    otherLevelsData(projectId, nodeData.getId(), item.getName(), item.getPath(), index);
                }

            } else {
                //第一层属于节点，同级别的文件不做处理
                continue;
//                //获取节点名称
//                String nodeName = getLastPathSegment(item.getPath());
//                //获取节点信息 主要用到ID
//                QueryWrapper<Nodes> queryWrapper = new QueryWrapper<>();
//                queryWrapper.eq("node_name", nodeName);
//                queryWrapper.eq("parent_id", TOP_LEVEL_PARENT_NODE);
//                Nodes node = nodesMapper.selectOne(queryWrapper);
//
//                //新增之前先查询
//                LambdaQueryWrapper<Files> queryWrapper1 = new LambdaQueryWrapper<>();
//                queryWrapper1.eq(Files::getProjectId, projectId);
//                queryWrapper1.eq(Files::getNodeId, node.getId());
//                queryWrapper1.eq(Files::getFilePath, item.getPath());
//                queryWrapper1.eq(Files::getFileName, item.getName());
//                Files files = filesMapper.selectOne(queryWrapper1);
//                if (files == null) {
//                    // 获取文件大小（字节）
//                    long fileSizeInBytes = item.getSize();
//                    // 转换为 MB 并保留两位小数
//                    double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0);
//                    String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数
//                    //保存文件信息
//                    saveFiles(projectId, node.getId(), item.getPath(), item.getName(), fileSizeFormatted);
//                }
            }
        }
    }

    /**
     * @param projectId 项目的ID
     * @param parentId  父级ID
     * @param nodeName  节点名称
     * @param path      路径
     * @return
     */
    private void otherLevelsData(String projectId, String parentId, String nodeName, String path, int index) throws Exception {

        index++;

        String absolutePath = path + nodeName + "/";
        //获取文件列表
        FileListRequest fileListRequest = buildFileRequest(absolutePath);
        String storageKey = fileListRequest.getStorageKey();
        Integer storageId = storageSourceService.findIdByKey(storageKey);
        if (storageId == null) {
            throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey);
        }
        // 处理请求参数默认值
        fileListRequest.handleDefaultValue();
        AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageId(storageId);
        //todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件，递归增加
        List<FileItemResult> fileItemList = fileService.fileList(fileListRequest.getPath());

        for (FileItemResult item : fileItemList) {
            //思路就是 如果是文件夹  就查询一下 没有就新增， 新的的时候递归往下走
            if (item.getType() == FileTypeEnum.FOLDER) {

                //先查询有没有 如果没有就新增
                LambdaQueryWrapper<Nodes> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(Nodes::getProjectId, projectId);
                queryWrapper.eq(Nodes::getParentId, parentId);
                queryWrapper.eq(Nodes::getNodeName, item.getName());
//                queryWrapper.eq(Nodes::getNodeOrder, obtainNodeType(index));
                Nodes nodeData = nodesMapper.selectOne(queryWrapper);
                //如果没有 新增 并且递归
                if (nodeData == null) {
                    //保存节点
                    Nodes node = saveNodes(projectId, parentId, item.getName(), obtainNodeType(index));
                    otherLevelsData(projectId, node.getId(), item.getName(), item.getPath(), index);
                } else {
                    otherLevelsData(projectId, nodeData.getId(), item.getName(), item.getPath(), index);
                }
            } else {
                //todo  如果是文件 直接新增就可以了 不需要其他的操作


                //新增之前先查询
                LambdaQueryWrapper<Files> queryWrapper1 = new LambdaQueryWrapper<>();
                queryWrapper1.eq(Files::getProjectId, projectId);
                queryWrapper1.eq(Files::getNodeId, parentId);
                queryWrapper1.eq(Files::getFilePath, item.getPath());
                queryWrapper1.eq(Files::getFileName, item.getName());
                Files files = filesMapper.selectOne(queryWrapper1);
                if (files == null) {
                    // 获取文件大小（字节）
                    long fileSizeInBytes = item.getSize();
                    // 转换为 MB 并保留两位小数
                    double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0);
                    String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数
                    //保存文件
                    saveFiles(projectId, parentId, item.getPath(), item.getName(), fileSizeFormatted);
                }

            }
        }
    }


    private String obtainNodeType(int index) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "01");
        map.put(2, "02");
        map.put(3, "03");
        map.put(4, "04");
        // 使用 Math.min 和 Math.max 保证 index 在 [1,4] 范围内
        int adjustedIndex = Math.min(Math.max(index, 1), 4);
        return map.get(adjustedIndex);
    }

    private FileListRequest buildFileRequest(String path) {
        FileListRequest fileListRequest = new FileListRequest();
        fileListRequest.setOrderBy("time");
        fileListRequest.setOrderDirection("desc");
        fileListRequest.setPassword("");
        fileListRequest.setPath(path);
        fileListRequest.setStorageKey("sdlocal");

        return fileListRequest;
    }

    /**
     * @param projectId 项目ID
     * @param parentId  父节点ID
     * @param nodeName  节点名称
     * @param nodeType  节点类型
     * @return
     */
    private Nodes saveNodes(String projectId, String parentId, String nodeName, String nodeType) {
        // 设置当前时间
        LocalDateTime now = LocalDateTime.now();
        // 转换为 Timestamp
        Timestamp currentTime = Timestamp.valueOf(now);
        //获取当前登录用户
        QueryWrapper<Nodes> queryWrapperNodeOrder = new QueryWrapper<>();
        int orderno = this.count(queryWrapperNodeOrder.eq("parent_id", TOP_LEVEL_PARENT_NODE)) + 1;

        Nodes node = new Nodes();
        node.setProjectId(projectId);
        node.setParentId(parentId);
        node.setNodeName(nodeName);
        node.setNodeType(nodeType);
        node.setCreateTime(currentTime);
        node.setNodeOrder(orderno);
        nodesMapper.insert(node);
        return node;
    }

    /**
     * 保存文件
     *
     * @param projectId
     * @param nodeId
     * @param filePath
     * @param fileName
     * @param fileSize
     */
    private void saveFiles(String projectId, String nodeId, String filePath, String fileName, String fileSize) {
        //获取当前登录用户         上传人是当前登录人
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginuser = null;
        if (authentication != null) {
            loginuser = (LoginUser) authentication.getPrincipal();
        }

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

        Files files = new Files();
        files.setProjectId(projectId);
        files.setNodeId(nodeId);
        files.setFilePath(filePath);
        files.setUploadTime(currentTime);
        if (loginuser == null) {
            files.setUploader(null);
        } else {
            files.setUploader(loginuser.getUsername());
        }
        files.setFileName(fileName);
        files.setFileSize(fileSize);
        filesMapper.insert(files);
    }

    /**
     * @param path
     * @return
     */
    public static String getLastPathSegment(String path) {
        if (path == null || path.trim().isEmpty()) {
            return "";
        }
        // 标准化路径处理
        String normalizedPath = path
                .replaceAll("\\\\+", "/")  // 替换所有反斜杠为斜杠
                .replaceAll("/+", "/")     // 合并连续斜杠
                .replaceAll("^/|/$", "");  // 去除首尾斜杠

        // 分割路径并过滤空段
        List<String> segments = Arrays.stream(normalizedPath.split("/"))
                .filter(segment -> !segment.isEmpty())
                .collect(Collectors.toList());

        return segments.isEmpty() ? "" : segments.get(segments.size() - 1);
    }


    @Override
    @Async("asyncExecutor")
    public void documentUploadByIdAsync(String id, String fileName) throws IOException {
        try {

            // 查询本地文件路径根目录（如 E:\yun）
            QueryWrapper<StorageSourceConfig> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("name", "filePath");
            queryWrapper.eq("type", "sdlocal");
            StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper);

            //解压以后的文件夹名称
            String zipName = getFileNameWithoutExtension(fileName); // 示例：222

            //查询项目信息
            Project project = projectMapper.selectById(id);

            if (!project.getProjectName().equals(zipName)) {
                throw new RuntimeException("压缩包名称需要和项目名称保持一致");
            }

            String decompressionPath = "/" + "temporary";
            //构建要解压的zip文件路径  例如   D:\yun\temporary\111.zip
            Path zipFilePath = Paths.get(storageSourceConfig.getValue(), decompressionPath, fileName);
            // 4. 构建解压目标路径  例如 D:\yun\temporary\111
            Path destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath);

            LOGGER.info("要解压文件路径：{}", zipFilePath);
            LOGGER.info("解压以后文件路径：{}", destRoot);


            //todo 如果项目不存在 直接拷贝到目标路径下  拷贝完成以后 然后新增一个项目结构  然后走一遍扫描

            //源文件夹路径
            String sourceFolderPath = "/" + "temporary" + "/" + zipName + "/";
            //目标文件夹路径
            String targetFolderPath = "/" + project.getProjectName() + "/";

            //首先执行解压缩
            unzipToTemp(zipFilePath, String.valueOf(destRoot));
            // 执行上传并且插入数据库
            this.documentUploadById(id, sourceFolderPath, targetFolderPath, storageSourceConfig.getValue());
            //开始执行更新表数据  名称是去掉后缀以后的
            uploadProject(zipName);
            LOGGER.info("开始更新表数据：{}", zipName);

            //获取文件路径
            Path destRootPath = Paths.get(storageSourceConfig.getValue(), decompressionPath, zipName);
            File targetZip = new File(String.valueOf(zipFilePath));
            File target = new File(String.valueOf(destRootPath));
            //删除临时文件ZIP
            deleteDirectory(targetZip);
            //删除临时文件
            deleteDirectory(target);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 生成唯一Key
            String asyncKey = taskStatusHolder.documentUploadKey(id);
            // 无论成功失败都标记完成
            taskStatusHolder.finishTask(asyncKey);
            WebSocketServer.sendMessageTo("专项文档上传任务处理完成!", "projectId_"+id);

        }
    }


    public static void deleteDirectory(File directory) throws Exception {
        if (!directory.exists()) {
            return; // 路径不存在直接返回
        }

        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    deleteDirectory(file); // 递归删除子目录
                } else {
                    if (!file.delete()) {   // 删除文件
                        throw new Exception("无法删除文件: " + file.getAbsolutePath());
                    }
                }
            }
        }

        if (!directory.delete()) { // 删除空目录
            throw new Exception("无法删除目录: " + directory.getAbsolutePath());
        }
    }

    /**********************************
     * 用途说明: 专项文档管理扫描接口实际接口
     * 参数说明  id 所属项目ID
     * 参数说明  destRoot 解压以后存放的路径
     * 参数说明  zipName  解压以后的文件夹名称
     * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
     ***********************************/
    public String documentUploadById(String id, String sourceFolderPath, String targetFolderPath, String value) throws Exception {

        //源目录
        File sourceDir = new File(value + sourceFolderPath);
        //目标目录
        File targetDir = new File(value + targetFolderPath);

        if (!targetDir.exists()) {
            FileUtils.copyDirectory(sourceDir, targetDir);
            LOGGER.info("目标文件没有,全部更新过去");
            return null;
        }

        FileListRequest sourcefileListRequest = buildFileRequest(sourceFolderPath);
        FileListRequest targetfileListRequest = buildFileRequest(targetFolderPath);

        //获取源文件列表 包含文件和文件夹
        List<FileItemResult> sourceFileItemList = obtainFileItemResultData(sourcefileListRequest);

        //获取目标文件列表 包含文件和文件夹
        List<FileItemResult> targetFileItemList = obtainFileItemResultData(targetfileListRequest);

        for (FileItemResult fileItemResult : sourceFileItemList) {

            String a1 = fileItemResult.getType() + fileItemResult.getName();
            FileItemResult targetFileItem = targetFileItemList.stream()
                    .filter(t -> (t.getType() + t.getName()).equals(a1))
                    .findFirst()  // 获取第一个匹配项（返回 Optional）
                    .orElse(null);

            // 检查对象存在
            if (targetFileItem != null) {
                //如果存在 判断是文件夹还是文件  如果是文件夹  则继续递归
                if (fileItemResult.getType() == FileTypeEnum.FOLDER) {
                    try {
                        documentUploadById(id, fileItemResult.getPath() + "/" + fileItemResult.getName(), targetFileItem.getPath() + "/" + targetFileItem.getName(), value);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    if (fileItemResult.getType() == FileTypeEnum.FILE) {
                        //源路径
                        String sourcePath = value + sourceFolderPath + "/" + fileItemResult.getName();

                        //目标路径
                        String targetPath = value + targetFolderPath + "/" + fileItemResult.getName();

                        //根据路径获取MD5值比较 如果相同则不进行操作  如果不同则重命名目标路径下的文件名称以及表结构  把现在源路径下的文件拷贝到目标路径下 新增数据库文件记录
                        // 计算源文件MD5
                        File sourceLocalFile = new File(sourcePath);
                        String sourceLocalMD5 = calculateMD5Data(new FileInputStream(sourceLocalFile));

                        // 计算源文件MD5
                        File targetLocalFile = new File(targetPath);
                        String targetLocalMD5 = calculateMD5Data(new FileInputStream(targetLocalFile));

                        // 如果MD5值不相同则进行操作 //如果一致 则直接复制源文件到目标文件
                        if (StringUtils.isNoneEmpty(sourceLocalMD5, targetLocalMD5) && !sourceLocalMD5.equals(targetLocalMD5)) {
                            LOGGER.info("MD5值不一样的路径" + sourcePath);
                            LOGGER.info("MD5值不一样的替换路径" + targetPath);
                            //拷贝文件到目标目录  将原来的文件名更改以及将数据库表结构更改
                            //更改目标路径下的文件名称
                            String newTargetPath = renameFile(sourcePath);
                            //将源目录文件 复制到 目标目录文件
                            copyToDirectoryFile(newTargetPath, value + targetFolderPath + "/");

                        } else {
                            //将源目录文件 复制到 目标目录文件 todo这个地方是覆盖
                            copyWithOverride(sourcePath, targetPath);
                        }
                    }
                }
            } else {
                //如果为空对象不存在 则直接拷贝
                String sourcePath = value + sourceFolderPath;
                //目标路径 就是源路径去掉/temporary的部分
                String targetPath = sourcePath.replace("/" + "temporary", "");
                //将源目录文件 复制到 目标目录文件
                copyFolderWithOverride(sourcePath, targetPath);

            }
        }


        return "上传完成";
    }

    private static void copyToDirectoryFile(String source, String targetDirectory) throws IOException {
        File srcFile = new File(source);
        File destDir = new File(targetDirectory);

        // 确保目标目录存在，如果不存在则创建
        if (!destDir.exists()) {
            destDir.mkdirs();
        }

        // 创建目标文件对象（在目标目录下使用源文件名）
        File destFile = new File(destDir, srcFile.getName());

        // 复制文件到目标目录
        FileUtils.copyFile(srcFile, destFile);
        System.out.println("成功复制文件到: " + destFile.getAbsolutePath());
    }


    public String uploadProject(String projectName) throws Exception {

        //查询项目信息
        LambdaQueryWrapper<Project> projectLambdaQueryWrapper = new LambdaQueryWrapper<>();
        projectLambdaQueryWrapper.eq(Project::getProjectName, projectName);
        Project project = projectMapper.selectOne(projectLambdaQueryWrapper);
        //如果项目不存在
        if (project == null) {
            String projectCode = this.generateNextProjectCode();
            project.setProjectCode("1");

            // 设置当前时间
            LocalDateTime now = LocalDateTime.now();
            // 转换为 Timestamp
            Timestamp currentTime = Timestamp.valueOf(now);
            project.setCreateTime(currentTime);
            int valueAdded = projectMapper.insert(project);
            if (valueAdded == 1) {

                // 根节点路径
                String path = "/";
                //新增节点的时候 创建文件夹
                NewFolderRequest newFolderRequest = new NewFolderRequest();
                newFolderRequest.setName(project.getProjectName());//新建的文件夹名称,示例值(/a/b/c)
                newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问，则支持请求密码,示例值(123456)
                newFolderRequest.setPath(path);//请求路径,示例值(/)
                newFolderRequest.setStorageKey("sdlocal");//存储源 key,示例值(local minio)
                AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey());
                boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName());
            }
        }
       specialScanById(project.getId());

        return "扫描完成";
    }


    // 编号生成工具方法
    @Transactional(rollbackFor = Exception.class)
    public synchronized String generateNextProjectCode() {
        // 1. 使用悲观锁查询最大编号
        Project maxProject = projectMapper.selectOne(
                new QueryWrapper<Project>()
                        .select("MAX(project_code) AS projectCode")
                        .last("FOR UPDATE")
        );

        // 2. 处理空表情况
        String currentMax = (maxProject != null && maxProject.getProjectCode() != null)
                ? maxProject.getProjectCode()
                : INITIAL_CODE;

        // 3. 验证并生成新编号
        try {
            int numericCode = Integer.parseInt(currentMax);
            if (numericCode < 0 || numericCode > MAX_CODE_VALUE) {
                throw new IllegalStateException("当前项目编号超出有效范围: " + currentMax);
            }
            if (numericCode == MAX_CODE_VALUE) {
                throw new IllegalStateException("项目编号已达最大值" + MAX_CODE_VALUE);
            }
            return String.format("%05d", numericCode + 1);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("无效的项目编号格式: " + currentMax);
        }

    }


    private static void copyFolderWithOverride(String source, String target) throws IOException {
        File srcDir = new File(source);
        File destDir = new File(target);

        // 删除目标文件夹（确保完全覆盖）
//        if (destDir.exists()) {
//            FileUtils.deleteDirectory(destDir);
//        }

        // 递归拷贝文件夹及内容
        FileUtils.copyDirectory(srcDir, destDir);
        System.out.println("成功覆盖文件夹: " + target);
    }

    private static void copyWithOverride(String source, String target) throws IOException {
        File srcFile = new File(source);
        File destFile = new File(target);

        // 强制覆盖已存在的文件
        FileUtils.copyFile(srcFile, destFile, true);
        System.out.println("成功覆盖文件: " + target);
    }

    public static String renameFile(String originalPath) throws IOException {
        File originalFile = new File(originalPath);
        if (!originalFile.exists()) {
            throw new IOException("原文件不存在: " + originalPath);
        }

        // 生成新文件名（保留扩展名）
        String baseName = FilenameUtils.getBaseName(originalPath);
        String extension = FilenameUtils.getExtension(originalPath);
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        String newFileName = baseName + "_" + timestamp + "." + extension;

        Path originalFilePath = originalFile.toPath();
        Path newFilePath = Paths.get(originalFile.getParent(), newFileName);

        // 处理目标文件已存在的情况
        if (java.nio.file.Files.exists(newFilePath)) {
            java.nio.file.Files.delete(newFilePath); // 删除已存在的目标文件
        }

        // 移动文件
        java.nio.file.Files.move(originalFilePath, newFilePath);
        System.out.println("重命名成功: " + originalPath + " → " + newFilePath.toString());
        return newFilePath.toString();
    }


    // 优化后的 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 List<FileItemResult> obtainFileItemResultData(FileListRequest fileListRequest) throws Exception {

        String storageKey = fileListRequest.getStorageKey();
        Integer storageId = storageSourceService.findIdByKey(storageKey);
        if (storageId == null) {
            throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey);
        }
        // 处理请求参数默认值
        fileListRequest.handleDefaultValue();
        AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageId(storageId);
        //todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件，递归增加
        List<FileItemResult> fileItemList = fileService.fileList(fileListRequest.getPath());
        return fileItemList;
    }

    // 新增：统一解压入口方法（保持原有逻辑结构）

    /**
     * @param zipFilePath 要解压的zip文件的路径 /path/to/archive.zip
     * @param baseDir     解压后的文件存放的根目录路径
     * @return
     * @throws IOException
     */
    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 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 ""; // 如果没有扩展名，返回空字符串
    }

    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);
    }
    //java.nio.file.Files


    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);
        }
    }

}

