java通过共享目录协议下载文件到本地

1、前端效果

2、前端代码(html)

html 复制代码
<template name="shared_directory_download">
    <div class="row" ng-controller="sharedDirectoryDownloadController as ctrl">
        <div class="col-12">
            <div>
                <!--共享目录-->
                <cb-title sub-title="仅支持文件下载本地"
                          title="共享目录下载文件">
                </cb-title>
                <div class="col-6">
                    共享ip:
                    <cb-flow-value-input ng-model="entity.ip"></cb-flow-value-input>
                </div>
                <div class="col-6">
                    用户名:
                    <cb-flow-value-input ng-model="entity.username"></cb-flow-value-input>
                </div>
                <div class="col-6">
                    密码:
                    <cb-flow-value-input ng-model="entity.password"></cb-flow-value-input>
                </div>
                <div class="col-6">
                    读取模式:
                    <cb-select ng-model="entity.ftpType" options="typeOptions"></cb-select>
                </div>
                <div class="col-6" ng-show="entity.ftpType.id == '1'||entity.ftpType.id == '2'">
                    文件名:
                    <cb-flow-value-input ng-model="entity.fileName"></cb-flow-value-input>
                </div>
                <div class="col-6" ng-show="entity.ftpType.id == '5'">
                    过滤已下载的文件:
                    <cb-flow-value-input ng-model="entity.downloadedList" title="用于过滤已下载的文件,格式要求List<String>"></cb-flow-value-input>
                </div>
                <div class="col-6">
                    目录路径:
                    <cb-flow-value-input ng-model="entity.directoryPath" title="和ip拼接访问共享目录,如//10.50.77.175/PdInfo"></cb-flow-value-input>
                </div>
                <div class="col-6">
                    本地保存路径:
                    <cb-flow-value-input ng-model="entity.localFilePath"></cb-flow-value-input>
                </div>
                <div class="col-6">
                    限制文件后缀:
                    <cb-flow-value-input ng-model="entity.fileExtension"
                                         title="限制读取的文件后缀,如:.log 空表示不限制"></cb-flow-value-input>
                </div>
                <div class="col-6">
                    排除文件名:
                    <cb-flow-value-input ng-model="entity.excludeFileName"
                                         title="要排除的文件名(如 Total.log 空表示不排除任何文件)"></cb-flow-value-input>
                </div>
                <div class="col-6">
                    ip在线校验超时时间:
                    <cb-flow-value-input ng-model="entity.timeoutMillis"
                                         title="用于控制校验ip是否在线校验超时时间毫秒,默认1s"></cb-flow-value-input>
                </div>
            </div>
        </div>
    </div>
</template>

3、前端代码(js)

javascript 复制代码
define([], function () {
    var app = angular.module('app');

    app.controller('sharedDirectoryDownloadController', ['$scope', 'dialog', '$timeout',
        'http', 'util',
        function ($scope, dialog, $timeout, http, util,) {
            var ctrl = this;

            $scope.typeOptions = [{
                id: '1',
                name: '精确名字匹配'
            }, {
                id: '2',
                name: '模糊名字匹配'
            }, {
                id: '3',
                name: '目录下第一个文件'
            }, {
                id: '4',
                name: '目录下最新一个文件'
            }, {
                id: '5',
                name: '目录下所有最新文件'
            }, {
                id: '6',
                name: '目录下全部文件'
            }];


            _.extend(ctrl, {
                initialize: function () {
                    ctrl.bindEvent();
                    ctrl.initEntity();
                },
                initEntity() {
                    if (_.isEmpty($scope.entity)) {
                        $scope.entity = {
                            open: true,
                            dataPointList: [],
                            contents: {}
                        }
                    }
                    util.apply($scope);
                },
                checkSave: function () {
                    if ($scope.entity.open) {
                        /*检查必备的配置*/
                    }
                },
                bindEvent: function () {
                    $scope.$on("onSave", function (event, data, checkMessage) {
                        var result = ctrl.checkSave(data, checkMessage);
                        if (result === false) {
                            return;
                        }
                    });
                }
            });
            ctrl.initialize();
        }]);
})

4、后端

javascript 复制代码
package com.skybird.iot.addons.flow.flowCreator.web.nodes.fileManage.backend;

import cn.hutool.core.util.StrUtil;
import com.skybird.commons.mongo.DocuLib;
import com.skybird.iot.addons.flow.flowCreator.backend.base.service.FlowNodeRunBaseService;
import com.skybird.iot.addons.flow.flowCreator.backend.dto.FlowRunDataDto;
import com.skybird.iot.addons.flow.flowCreator.backend.util.FlowFtpUtils;
import com.skybird.iot.addons.flow.flowCreator.backend.util.FlowProtocolAccessLogUtil;
import com.skybird.iot.addons.flow.flowCreator.backend.util.FlowSharedDirectoryUtils;
import com.skybird.iot.addons.flow.flowCreator.web.nodes.fileManage.backend.service.FlowOssService;
import jakarta.annotation.Resource;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class FlowSharedDirectoryDownloadService extends FlowNodeRunBaseService {
    private static final Logger LOGGER =
            LoggerFactory.getLogger(FlowSharedDirectoryDownloadService.class.getName());
    @Resource
    FlowFolderJudgmentReadService flowFolderJudgmentReadService;

    @Override
    public String[] getComponentKey() {
        return new String[]{"shared_directory_download"};
    }

    @Override
    public void run(FlowRunDataDto flowRunDataDto) throws Exception {

        Document nodeConfig = flowRunDataDto.getNodeConfig();

        String ipRow = String.valueOf(flowRunDataDto.getFieldValue(DocuLib.getDocu(nodeConfig, "ip")));
        String username = String.valueOf(flowRunDataDto.getFieldValue(DocuLib.getDocu(nodeConfig, "username")));
        String password = String.valueOf(flowRunDataDto.getFieldValue(DocuLib.getDocu(nodeConfig, "password")));
        String fileName = String.valueOf(flowRunDataDto.getFieldValue(DocuLib.getDocu(nodeConfig, "fileName")));
        String localFilePath = String.valueOf(flowRunDataDto.getFieldValue(DocuLib.getDocu(nodeConfig, "localFilePath")));
        String directoryPath = String.valueOf(flowRunDataDto.getFieldValue(DocuLib.getDocu(nodeConfig, "directoryPath")));
        String fileExtension = String.valueOf(flowRunDataDto.getFieldValue(DocuLib.getDocu(nodeConfig, "fileExtension")));
        String excludeFileName = String.valueOf(flowRunDataDto.getFieldValue(DocuLib.getDocu(nodeConfig, "excludeFileName")));
        String timeoutMillis1 = String.valueOf(flowRunDataDto.getFieldValue(DocuLib.getDocu(nodeConfig, "timeoutMillis")));
        if (StrUtil.isEmpty(timeoutMillis1) || timeoutMillis1.startsWith("Document{")) {
            timeoutMillis1 = "1000";
        }
        Integer timeoutMillis = Integer.parseInt(timeoutMillis1);//ftp ip校验超时时间毫秒
        String ftpType = DocuLib.getStr(nodeConfig, "ftpType.id");
        Document result = DocuLib.newDoc();
        if (FlowSharedDirectoryUtils.pingHost(ipRow, timeoutMillis)) {
            if (StrUtil.equals(ftpType, "1")) {
                Document dto = FlowSharedDirectoryUtils.downloadFileByName(ipRow, directoryPath, username, password, localFilePath, fileExtension, excludeFileName, fileName);
                result.putAll(dto);
            } else if (StrUtil.equals(ftpType, "2")) {
                Document dto = FlowSharedDirectoryUtils.vagueDownloadFile(ipRow, directoryPath, username, password, localFilePath, fileExtension, excludeFileName, fileName);
                result.putAll(dto);
            } else if (StrUtil.equals(ftpType, "4")) {
                Document dto = FlowSharedDirectoryUtils.listDownloadGetLatestFile(ipRow, directoryPath, username, password, localFilePath, fileExtension, excludeFileName);
                result.putAll(dto);
            } else if (StrUtil.equals(ftpType, "5")) {
                Object downloadedListObj = flowRunDataDto.getFieldValue(DocuLib.getDocu(nodeConfig, "downloadedList"));
                List<String> downloadedList = null;
                if (downloadedListObj instanceof List) {
                    try {
                        downloadedList = ((List<?>) downloadedListObj).stream()
                                .map(Object::toString)
                                .toList();
                    } catch (Exception e) {
                        result.append("message", "过滤已下载的文件请传List<String>");
                    }
                }
                Document dto = FlowSharedDirectoryUtils.listDownloadGetAllLatestDateFiles(ipRow, directoryPath, username, password, localFilePath, fileExtension, excludeFileName,downloadedList);
                result.putAll(dto);
            } else if (StrUtil.equals(ftpType, "6")) {
                Document dto = FlowSharedDirectoryUtils.downloadAllFile(ipRow, directoryPath, username, password, localFilePath, fileExtension, excludeFileName);
                result.putAll(dto);
            } else {
                Document dto = FlowSharedDirectoryUtils.listDownloadGetFirstFile(ipRow, directoryPath, username, password, localFilePath, fileExtension, excludeFileName);
                result.putAll(dto);
            }
        } else {
            result.append("message", "请检查网络情况,IP未通!");
        }

        flowRunDataDto.nodeValueAppend(new Document().append("result", result));
        FlowProtocolAccessLogUtil.addProtocolReadLog(nodeConfig, result);
    }

    @Override
    public void unRegister() {

    }
}

5、后端封装工具类方法

java 复制代码
package com.skybird.iot.addons.flow.flowCreator.backend.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RuntimeUtil;
import com.skybird.commons.mongo.DocuLib;
import org.bson.Document;

import java.io.*;
import java.net.InetAddress;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;


public class FlowSharedDirectoryUtils {

    /**
     * 判断IP地址是否可达
     *
     * @param ip
     * @param timeoutMillis 超时时间毫秒
     * @return
     */
    public static boolean isHostReachable(String ip, int timeoutMillis) {
        try {
            return InetAddress.getByName(ip).isReachable(timeoutMillis);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 判断IP地址是否可达 ping方式
     *
     * @param ipOrHost
     * @param timeoutMs
     * @return
     */
    public static boolean pingHost(String ipOrHost, int timeoutMs) {
        String command = System.getProperty("os.name").toLowerCase().contains("win")
                ? "ping -n 1 -w " + timeoutMs + " " + ipOrHost
                : "ping -c 1 -W " + (timeoutMs / 1000) + " " + ipOrHost;

        String result = RuntimeUtil.execForStr(command);
        return result.contains("TTL=") || result.contains("ttl=");
    }

    /**
     * 从共享目录下载最新修改的指定后缀文件
     *
     * @param ip              服务器IP地址
     * @param sharePath       共享路径
     * @param username        用户名
     * @param password        密码
     * @param localPath       本地保存路径
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @return 下载的文件路径
     * @throws IOException IO异常
     */
    public static Document listDownloadGetLatestFile(String ip, String sharePath, String username, String password,
                                                     String localPath, String fileExtension, String excludeFileName) {
        try {
            // 构造完整的共享路径,自动识别路径分隔符
            String fullSharePath = String.format("\\\\%s\\%s", ip, sharePath);
            Path networkPath = Paths.get(fullSharePath);

            // 检查共享目录是否存在
            if (!Files.exists(networkPath)) {
                // 尝试映射网络驱动器(在Windows环境下)
                Document document = mapNetworkDrive(fullSharePath, username, password);
                // 返回错误信息
                if (DocuLib.isNotEmpty(document)) {
                    return document;
                }
                // 重新检查
                if (!Files.exists(networkPath)) {
                    return DocuLib.newDoc("message", "无法访问共享目录: " + fullSharePath);
                }
            }

            Path latestFile = findLatestFile(networkPath, fileExtension, excludeFileName);

            if (latestFile == null) {
                if (fileExtension != null) {
                    return DocuLib.newDoc("message", "无法找到最新" + fileExtension + "文件");
                } else {
                    return DocuLib.newDoc("message", "无法找到最新文件");
                }
            }

            // 创建目标目录
            createDirectory(localPath);

            // 获取文件名并构造目标路径,添加UUID避免冲突
            String fileName = latestFile.getFileName().toString();
            String fileNameWithoutExtension = getFileBaseName(fileName);
            String fileExtensionName = getFileExtension(fileName);
            String uniqueFileName = fileNameWithoutExtension + "_" + generateHexId() + fileExtensionName;
            Path targetPath = Paths.get(localPath, uniqueFileName);

            // 复制文件到本地
            Files.copy(latestFile, targetPath, StandardCopyOption.REPLACE_EXISTING);
            // 将修改时间转换为字符串格式
            LocalDateTime modifiedTime = LocalDateTime.ofInstant(
                    Files.getLastModifiedTime(latestFile).toInstant(),
                    ZoneId.systemDefault()
            );
            String formattedTime = modifiedTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            Document dto = DocuLib.newDoc("filePath", targetPath.toString());
            dto.put("updateTime", formattedTime);
            return dto;
        } catch (IOException e) {
            e.printStackTrace();
            return DocuLib.newDoc("message", "下载文件时发生错误: " + e.getMessage());
        }
    }

    /**
     * 从共享目录获取最新日期的所有文件下载到本地
     *
     * @param ip              服务器IP地址
     * @param sharePath       共享路径
     * @param username        用户名
     * @param password        密码
     * @param localPath       本地保存路径
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @param downloadedList  已经下载过的文件名列表
     * @return 下载结果,包含所有下载文件的路径列表
     * @throws IOException IO异常
     */
    public static Document listDownloadGetAllLatestDateFiles(String ip, String sharePath, String username, String password,
                                                             String localPath, String fileExtension, String excludeFileName, List<String> downloadedList) {
        try {
            // 构造完整的共享路径,自动识别路径分隔符
            String fullSharePath = String.format("\\\\%s\\%s", ip, sharePath);
            Path networkPath = Paths.get(fullSharePath);

            // 检查共享目录是否存在
            if (!Files.exists(networkPath)) {
                // 尝试映射网络驱动器(在Windows环境下)
                Document document = mapNetworkDrive(fullSharePath, username, password);
                // 返回错误信息
                if (DocuLib.isNotEmpty(document)) {
                    return document;
                }
                // 重新检查
                if (!Files.exists(networkPath)) {
                    return DocuLib.newDoc("message", "无法访问共享目录: " + fullSharePath);
                }
            }

            // 查找最新日期的所有文件
            List<Path> latestDateFiles = findAllLatestDateFiles(networkPath, fileExtension, excludeFileName);
            // 过滤掉已下载的文件
            if (CollUtil.isNotEmpty(downloadedList)) {
                latestDateFiles.removeIf(file -> downloadedList.contains(file.getFileName().toString()));
            }
            if (latestDateFiles.isEmpty()) {
                if (fileExtension != null) {
                    return DocuLib.newDoc("message", "无法找到最新日期的" + fileExtension + "文件");
                } else {
                    return DocuLib.newDoc("message", "无法找到最新日期的文件");
                }
            }

            // 创建目标目录
            createDirectory(localPath);

            List<String> downloadedFiles = new ArrayList<>();
            String latestDate = "";

            // 下载所有最新日期的文件
            for (Path file : latestDateFiles) {
                try {
                    // 获取文件名并构造目标路径,添加UUID避免冲突
                    String fileName = file.getFileName().toString();
                    String fileNameWithoutExtension = getFileBaseName(fileName);
                    String fileExtensionName = getFileExtension(fileName);
                    String uniqueFileName = fileNameWithoutExtension + "_" + generateHexId() + fileExtensionName;
                    Path targetPath = Paths.get(localPath, uniqueFileName);

                    // 复制文件到本地
                    Files.copy(file, targetPath, StandardCopyOption.REPLACE_EXISTING);
                    downloadedFiles.add(targetPath.toString());

                    // 获取最新日期信息(所有文件日期相同,只需获取一次)
                    if (latestDate.isEmpty()) {
                        LocalDateTime modifiedTime = LocalDateTime.ofInstant(
                                Files.getLastModifiedTime(file).toInstant(),
                                ZoneId.systemDefault()
                        );
                        latestDate = modifiedTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                    }

                } catch (IOException e) {
                    return DocuLib.newDoc("message", "下载文件时出错:" + file.getFileName() + " " + e.getMessage());
                }
            }

            Document result = DocuLib.newDoc("filePath", downloadedFiles);
            result.put("updateTime", latestDate);

            return result;
        } catch (Exception e) {
            return DocuLib.newDoc("message", "下载文件时出错: " + e.getMessage());
        }
    }

    /**
     * 查找最新日期的所有文件(内存优化版本,适用于超大量文件场景)
     *
     * @param directory       目录路径
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @return 最新日期的所有文件路径列表
     * @throws IOException IO异常
     */
    private static List<Path> findAllLatestDateFiles(Path directory, String fileExtension, String excludeFileName) throws IOException {
        // 使用单次遍历找出最新时间并收集文件
        LocalDateTime[] latestDateTime = {null};
        List<Path> latestDateFiles = new ArrayList<>();

        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (attrs.isRegularFile()) {
                    String fileName = file.getFileName().toString();

                    // 检查是否需要排除该文件
                    if (excludeFileName != null && fileName.equalsIgnoreCase(excludeFileName)) {
                        return FileVisitResult.CONTINUE; // 跳过该文件
                    }
                    // 检查文件后缀是否匹配
                    if (fileExtension == null || fileName.toLowerCase().endsWith(fileExtension.toLowerCase())) {
                        try {
                            LocalDateTime fileDateTime = LocalDateTime.ofInstant(
                                    attrs.lastModifiedTime().toInstant(),
                                    ZoneId.systemDefault()
                            ).withNano(0); // 截断到秒级精度

                            // 如果发现新的最新时间,清空之前的列表并更新最新时间
                            if (latestDateTime[0] == null || fileDateTime.isAfter(latestDateTime[0])) {
                                latestDateTime[0] = fileDateTime;
                                latestDateFiles.clear(); // 清空之前的文件列表
                                latestDateFiles.add(file); // 添加当前文件
                            }
                            // 如果是相同时间的文件,添加到列表中
                            else if (fileDateTime.isEqual(latestDateTime[0])) {
                                latestDateFiles.add(file);
                            }
                        } catch (Exception e) {
                            // 忽略无法获取修改时间的文件
                        }
                    }
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                // 忽略无法访问的文件,继续处理其他文件
                return FileVisitResult.CONTINUE;
            }
        });

        return latestDateFiles;
    }


    /**
     * 从共享目录根据文件名获取相同文件名的文件下载到本地
     *
     * @param ip              服务器IP地址
     * @param sharePath       共享路径
     * @param username        用户名
     * @param password        密码
     * @param localPath       本地保存路径
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @param fileName        文件名
     * @return 下载的文件路径
     */
    public static Document downloadFileByName(String ip, String sharePath, String username, String password,
                                              String localPath, String fileExtension, String excludeFileName, String fileName) {
        try {
            // 构造完整的共享路径
            String fullSharePath = String.format("\\\\%s\\%s", ip, sharePath);
            Path networkPath = Paths.get(fullSharePath);

            // 检查共享目录是否存在
            if (!Files.exists(networkPath)) {
                // 尝试映射网络驱动器(在Windows环境下)
                Document document = mapNetworkDrive(fullSharePath, username, password);
                // 返回错误信息
                if (DocuLib.isNotEmpty(document)) {
                    return document;
                }
                // 重新检查
                if (!Files.exists(networkPath)) {
                    return DocuLib.newDoc("message", "无法访问共享目录: " + fullSharePath);
                }
            }

            // 查找指定文件名的文件
            Path targetFile = findFileByName(networkPath, fileName, fileExtension, excludeFileName);

            if (targetFile == null) {
                return DocuLib.newDoc("message", "无法找到文件: " + fileName);
            }

            // 创建目标目录
            createDirectory(localPath);

            // 获取文件名并构造目标路径,添加UUID避免冲突
            String targetFileName = targetFile.getFileName().toString();
            String fileNameWithoutExtension = getFileBaseName(targetFileName);
            String fileExtensionName = getFileExtension(targetFileName);
            String uniqueFileName = fileNameWithoutExtension + "_" + generateHexId() + fileExtensionName;
            Path targetPath = Paths.get(localPath, uniqueFileName);

            // 复制文件到本地
            Files.copy(targetFile, targetPath, StandardCopyOption.REPLACE_EXISTING);

            // 将修改时间转换为字符串格式
            LocalDateTime modifiedTime = LocalDateTime.ofInstant(
                    Files.getLastModifiedTime(targetFile).toInstant(),
                    ZoneId.systemDefault()
            );
            String formattedTime = modifiedTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

            Document dto = DocuLib.newDoc("filePath", targetPath.toString());
            dto.put("updateTime", formattedTime);
            dto.put("fileName", targetFileName);
            return dto;

        } catch (Exception e) {
            return DocuLib.newDoc("message", "下载文件时出错: " + e.getMessage());
        }
    }

    /**
     * 从共享目录获取第一个包含文件名的文件下载到本地
     *
     * @param ip              服务器IP地址
     * @param sharePath       共享路径
     * @param username        用户名
     * @param password        密码
     * @param localPath       本地保存路径
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @param fileName        文件名
     * @return 下载的文件路径
     */
    public static Document vagueDownloadFile(String ip, String sharePath, String username, String password,
                                             String localPath, String fileExtension, String excludeFileName, String fileName) {
        try {
            // 构造完整的共享路径
            String fullSharePath = String.format("\\\\%s\\%s", ip, sharePath);
            Path networkPath = Paths.get(fullSharePath);

            // 检查共享目录是否存在
            if (!Files.exists(networkPath)) {
                // 尝试映射网络驱动器(在Windows环境下)
                Document document = mapNetworkDrive(fullSharePath, username, password);
                // 返回错误信息
                if (DocuLib.isNotEmpty(document)) {
                    return document;
                }
                // 重新检查
                if (!Files.exists(networkPath)) {
                    return DocuLib.newDoc("message", "无法访问共享目录: " + fullSharePath);
                }
            }

            // 查找包含指定文件名的文件
            Path targetFile = findFileByVagueName(networkPath, fileName, fileExtension, excludeFileName);

            if (targetFile == null) {
                return DocuLib.newDoc("message", "无法找到包含文件名 '" + fileName + "' 的文件");
            }

            // 创建目标目录
            createDirectory(localPath);

            // 获取文件名并构造目标路径,添加UUID避免冲突
            String targetFileName = targetFile.getFileName().toString();
            String fileNameWithoutExtension = getFileBaseName(targetFileName);
            String fileExtensionName = getFileExtension(targetFileName);
            String uniqueFileName = fileNameWithoutExtension + "_" + generateHexId() + fileExtensionName;
            Path targetPath = Paths.get(localPath, uniqueFileName);

            // 复制文件到本地
            Files.copy(targetFile, targetPath, StandardCopyOption.REPLACE_EXISTING);

            // 将修改时间转换为字符串格式
            LocalDateTime modifiedTime = LocalDateTime.ofInstant(
                    Files.getLastModifiedTime(targetFile).toInstant(),
                    ZoneId.systemDefault()
            );
            String formattedTime = modifiedTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

            Document dto = DocuLib.newDoc("filePath", targetPath.toString());
            dto.put("updateTime", formattedTime);
            dto.put("fileName", targetFileName);
            return dto;

        } catch (Exception e) {
            return DocuLib.newDoc("message", "下载文件时出错: " + e.getMessage());
        }
    }

    /**
     * 从共享目录根据文件目录获取第一个文件下载到本地
     *
     * @param ip              服务器IP地址
     * @param sharePath       共享路径
     * @param username        用户名
     * @param password        密码
     * @param localPath       本地保存路径
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @return 下载的文件路径
     */
    public static Document listDownloadGetFirstFile(String ip, String sharePath, String username, String password,
                                                    String localPath, String fileExtension, String excludeFileName) {
        try {
            // 构造完整的共享路径
            String fullSharePath = String.format("\\\\%s\\%s", ip, sharePath);
            Path networkPath = Paths.get(fullSharePath);

            // 检查共享目录是否存在
            if (!Files.exists(networkPath)) {
                // 尝试映射网络驱动器(在Windows环境下)
                Document document = mapNetworkDrive(fullSharePath, username, password);
                // 返回错误信息
                if (DocuLib.isNotEmpty(document)) {
                    return document;
                }
                // 重新检查
                if (!Files.exists(networkPath)) {
                    return DocuLib.newDoc("message", "无法访问共享目录: " + fullSharePath);
                }
            }

            // 查找第一个文件
            Path firstFile = findFirstFile(networkPath, fileExtension, excludeFileName);

            if (firstFile == null) {
                if (fileExtension != null) {
                    return DocuLib.newDoc("message", "无法找到" + fileExtension + "文件");
                } else {
                    return DocuLib.newDoc("message", "无法找到文件");
                }
            }

            // 创建目标目录
            createDirectory(localPath);

            // 获取文件名并构造目标路径,添加UUID避免冲突
            String fileName = firstFile.getFileName().toString();
            String fileNameWithoutExtension = getFileBaseName(fileName);
            String fileExtensionName = getFileExtension(fileName);
            String uniqueFileName = fileNameWithoutExtension + "_" + generateHexId() + fileExtensionName;
            Path targetPath = Paths.get(localPath, uniqueFileName);

            // 复制文件到本地
            Files.copy(firstFile, targetPath, StandardCopyOption.REPLACE_EXISTING);

            // 将修改时间转换为字符串格式
            LocalDateTime modifiedTime = LocalDateTime.ofInstant(
                    Files.getLastModifiedTime(firstFile).toInstant(),
                    ZoneId.systemDefault()
            );
            String formattedTime = modifiedTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

            Document dto = DocuLib.newDoc("filePath", targetPath.toString());
            dto.put("updateTime", formattedTime);
            dto.put("fileName", fileName);
            return dto;

        } catch (Exception e) {
            return DocuLib.newDoc("message", "下载文件时出错: " + e.getMessage());
        }
    }

    /**
     * 从共享目录根据文件目录获取全部文件下载到本地
     *
     * @param ip              服务器IP地址
     * @param sharePath       共享路径
     * @param username        用户名
     * @param password        密码
     * @param localPath       本地保存路径
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @return 下载的文件路径
     */
    public static Document downloadAllFile(String ip, String sharePath, String username, String password,
                                           String localPath, String fileExtension, String excludeFileName) {
        try {
            // 构造完整的共享路径
            String fullSharePath = String.format("\\\\%s\\%s", ip, sharePath);
            Path networkPath = Paths.get(fullSharePath);

            // 检查共享目录是否存在
            if (!Files.exists(networkPath)) {
                // 尝试映射网络驱动器(在Windows环境下)
                Document document = mapNetworkDrive(fullSharePath, username, password);
                // 返回错误信息
                if (DocuLib.isNotEmpty(document)) {
                    return document;
                }
                // 重新检查
                if (!Files.exists(networkPath)) {
                    return DocuLib.newDoc("message", "无法访问共享目录: " + fullSharePath);
                }
            }

            // 查找所有符合条件的文件
            List<Path> allFiles = findAllFiles(networkPath, fileExtension, excludeFileName);

            if (allFiles.isEmpty()) {
                if (fileExtension != null) {
                    return DocuLib.newDoc("message", "无法找到任何" + fileExtension + "文件");
                } else {
                    return DocuLib.newDoc("message", "无法找到任何文件");
                }
            }

            // 创建目标目录
            createDirectory(localPath);

            List<String> downloadedFiles = new ArrayList<>();
            String latestDate = "";

            // 下载所有文件
            for (Path file : allFiles) {
                try {
                    // 获取文件名并构造目标路径,添加UUID避免冲突
                    String fileName = file.getFileName().toString();
                    String fileNameWithoutExtension = getFileBaseName(fileName);
                    String fileExtensionName = getFileExtension(fileName);
                    String uniqueFileName = fileNameWithoutExtension + "_" + generateHexId() + fileExtensionName;
                    Path targetPath = Paths.get(localPath, uniqueFileName);

                    // 复制文件到本地
                    Files.copy(file, targetPath, StandardCopyOption.REPLACE_EXISTING);
                    downloadedFiles.add(targetPath.toString());

                    // 获取最新修改时间
                    LocalDateTime modifiedTime = LocalDateTime.ofInstant(
                            Files.getLastModifiedTime(file).toInstant(),
                            ZoneId.systemDefault()
                    );
                    String formattedTime = modifiedTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

                    // 更新最新的文件时间
                    if (latestDate.isEmpty() || formattedTime.compareTo(latestDate) > 0) {
                        latestDate = formattedTime;
                    }

                } catch (IOException e) {
                    return DocuLib.newDoc("message", "下载文件时出错:" + file.getFileName() + " " + e.getMessage());
                }
            }

            Document result = DocuLib.newDoc("filePath", downloadedFiles);
            result.put("updateTime", latestDate);

            return result;

        } catch (Exception e) {
            return DocuLib.newDoc("message", "下载文件时出错: " + e.getMessage());
        }
    }

    /**
     * 查找所有符合条件的文件
     *
     * @param directory       目录路径
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @return 符合条件的所有文件路径列表
     * @throws IOException IO异常
     */
    private static List<Path> findAllFiles(Path directory, String fileExtension, String excludeFileName) throws IOException {
        List<Path> files = new ArrayList<>();

        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (attrs.isRegularFile()) {
                    String fileName = file.getFileName().toString();

                    // 检查是否需要排除该文件
                    if (excludeFileName != null && fileName.equalsIgnoreCase(excludeFileName)) {
                        return FileVisitResult.CONTINUE; // 跳过该文件
                    }
                    // 检查文件后缀是否匹配
                    if (fileExtension == null || fileName.toLowerCase().endsWith(fileExtension.toLowerCase())) {
                        files.add(file);
                    }
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                // 忽略无法访问的文件,继续处理其他文件
                return FileVisitResult.CONTINUE;
            }
        });

        return files;
    }


    /**
     * 映射网络驱动器
     *
     * @param sharePath 共享路径(格式:\\\server\share)
     * @param username  用户名
     * @param password  密码
     * @return 错误信息文档,如果成功则返回空文档
     */
    private static Document mapNetworkDrive(String sharePath, String username, String password) {
        Process process = null;
        try {
            // 1. 先尝试删除可能存在的旧连接(忽略错误)
            try {
                String deleteCommand = String.format("cmd.exe /c net use %s /delete /yes", sharePath);
                Process deleteProcess = Runtime.getRuntime().exec(deleteCommand);
                // 消耗掉所有输出,避免阻塞
                consumeStream(deleteProcess.getInputStream());
                consumeStream(deleteProcess.getErrorStream());
                deleteProcess.waitFor();
            } catch (Exception e) {
                // 忽略删除连接的错误
            }

            // 2. 建立新的连接,使用 ProcessBuilder 避免命令注入和空格问题
            // 使用数组形式传递参数,自动处理空格和特殊字符
            ProcessBuilder processBuilder = new ProcessBuilder(
                    "cmd.exe",
                    "/c",
                    "net",
                    "use",
                    sharePath,  // 路径本身(如 \\\\10.50.76.61\\My History)
                    password,   // 密码
                    "/user:" + username,  // 用户参数(如 /user:ftp)
                    "/persistent:no"
            );

            process = processBuilder.start();

            // 异步读取输出流,避免阻塞
            StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "GBK");
            StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "GBK");
            errorGobbler.start();
            outputGobbler.start();

            int result = process.waitFor();

            // 等待输出读取完成
            errorGobbler.join(2000);
            outputGobbler.join(2000);

            if (result != 0) {
                String errorMessage = errorGobbler.getOutput() + "\n" + outputGobbler.getOutput();
                errorMessage = errorMessage.trim();

                System.err.println("映射网络驱动器失败,退出码:" + result);
                System.err.println("错误详情:" + errorMessage);

                if (errorMessage.isEmpty()) {
                    errorMessage = "未知错误,请检查网络连接、共享服务器设置或凭据是否正确";
                }

                return DocuLib.newDoc("message", "映射网络驱动器失败:" + errorMessage);
            } else {
                System.out.println("成功映射网络驱动器:" + sharePath);
                return DocuLib.newDoc();
            }
        } catch (Exception e) {
            return DocuLib.newDoc("message", "映射网络驱动器时出错:" + e.getMessage());
        }
    }

    /**
     * 消耗流中的所有数据(用于避免进程阻塞)
     */
    private static void consumeStream(InputStream inputStream) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "GBK"));
            while (reader.read() != -1) {
                // 只是消耗掉,不处理
            }
            reader.close();
        } catch (Exception e) {
            // 忽略异常
        }
    }

    /**
     * 用于异步读取进程输出的辅助类
     */
    private static class StreamGobbler extends Thread {
        private final InputStream inputStream;
        private final String encoding;
        private final StringBuilder output = new StringBuilder();

        public StreamGobbler(InputStream inputStream, String encoding) {
            this.inputStream = inputStream;
            this.encoding = encoding;
        }

        @Override
        public void run() {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, encoding));
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line).append("\n");
                }
                reader.close();
            } catch (Exception e) {
                output.append("读取输出时出错:").append(e.getMessage());
            }
        }

        public String getOutput() {
            return output.toString().trim();
        }
    }

    /**
     * 获取文件名(不包含扩展名)
     *
     * @param fileName 完整文件名
     * @return 文件名(不包含扩展名)
     */
    private static String getFileBaseName(String fileName) {
        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex == -1) {
            return fileName;
        }
        return fileName.substring(0, lastDotIndex);
    }

    /**
     * 获取文件扩展名
     *
     * @param fileName 完整文件名
     * @return 文件扩展名(包含点号)
     */
    private static String getFileExtension(String fileName) {
        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex == -1) {
            return "";
        }
        return fileName.substring(lastDotIndex);
    }

    /**
     * 高效查找最新修改的文件
     *
     * @param directory       目录路径
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @return 最新修改的文件路径
     * @throws IOException IO异常
     */
    private static Path findLatestFile(Path directory, String fileExtension, String excludeFileName) throws IOException {
        Path[] latest = {null};
        long[] latestTime = {Long.MIN_VALUE};

        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (attrs.isRegularFile()) {
                    String fileName = file.getFileName().toString();

                    // 检查是否需要排除该文件
                    if (excludeFileName != null && fileName.equalsIgnoreCase(excludeFileName)) {
                        return FileVisitResult.CONTINUE; // 跳过该文件
                    }

                    // 检查文件后缀是否匹配
                    if (fileExtension == null || fileName.toLowerCase().endsWith(fileExtension.toLowerCase())) {
                        long modifiedTime = attrs.lastModifiedTime().toMillis();
                        if (modifiedTime > latestTime[0]) {
                            latestTime[0] = modifiedTime;
                            latest[0] = file;
                        }
                    }
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                // 忽略无法访问的文件,继续处理其他文件
                return FileVisitResult.CONTINUE;
            }
        });

        return latest[0];
    }

    /**
     * 根据文件名查找文件
     *
     * @param directory       目录路径
     * @param fileName        文件名
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @return 找到的文件路径
     * @throws IOException IO异常
     */
    private static Path findFileByName(Path directory, String fileName, String fileExtension, String excludeFileName) throws IOException {
        Path[] targetFile = {null};

        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (attrs.isRegularFile()) {
                    String currentFileName = file.getFileName().toString();

                    // 检查是否需要排除该文件
                    if (excludeFileName != null && currentFileName.equalsIgnoreCase(excludeFileName)) {
                        return FileVisitResult.CONTINUE; // 跳过该文件
                    }

                    // 检查文件名是否匹配
                    if (currentFileName.equalsIgnoreCase(fileName)) {
                        // 检查文件后缀是否匹配
                        if (fileExtension == null || currentFileName.toLowerCase().endsWith(fileExtension.toLowerCase())) {
                            targetFile[0] = file;
                            return FileVisitResult.TERMINATE; // 找到文件后终止遍历
                        }
                    }
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                // 忽略无法访问的文件,继续处理其他文件
                return FileVisitResult.CONTINUE;
            }
        });

        return targetFile[0];
    }


    /**
     * 根据模糊文件名查找文件
     *
     * @param directory       目录路径
     * @param fileName        文件名(部分匹配)
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @return 找到的文件路径
     * @throws IOException IO异常
     */
    private static Path findFileByVagueName(Path directory, String fileName, String fileExtension, String excludeFileName) throws IOException {
        Path[] targetFile = {null};

        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (attrs.isRegularFile()) {
                    String currentFileName = file.getFileName().toString();

                    // 检查是否需要排除该文件
                    if (excludeFileName != null && currentFileName.equalsIgnoreCase(excludeFileName)) {
                        return FileVisitResult.CONTINUE; // 跳过该文件
                    }

                    // 检查文件名是否包含指定字符串
                    if (currentFileName.toLowerCase().contains(fileName.toLowerCase())) {
                        // 检查文件后缀是否匹配
                        if (fileExtension == null || currentFileName.toLowerCase().endsWith(fileExtension.toLowerCase())) {
                            targetFile[0] = file;
                            return FileVisitResult.TERMINATE; // 找到文件后终止遍历
                        }
                    }
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                // 忽略无法访问的文件,继续处理其他文件
                return FileVisitResult.CONTINUE;
            }
        });

        return targetFile[0];
    }

    /**
     * 查找第一个文件
     *
     * @param directory       目录路径
     * @param fileExtension   文件后缀(如 ".log",null表示不限制后缀)
     * @param excludeFileName 要排除的文件名(如 "Total.log",null表示不排除任何文件)
     * @return 第一个文件路径
     * @throws IOException IO异常
     */
    private static Path findFirstFile(Path directory, String fileExtension, String excludeFileName) throws IOException {
        Path[] firstFile = {null};

        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (attrs.isRegularFile()) {
                    String fileName = file.getFileName().toString();

                    // 检查是否需要排除该文件
                    if (excludeFileName != null && fileName.equalsIgnoreCase(excludeFileName)) {
                        return FileVisitResult.CONTINUE; // 跳过该文件
                    }

                    // 检查文件后缀是否匹配
                    if (fileExtension == null || fileName.toLowerCase().endsWith(fileExtension.toLowerCase())) {
                        firstFile[0] = file;
                        return FileVisitResult.TERMINATE; // 找到文件后终止遍历
                    }
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                // 忽略无法访问的文件,继续处理其他文件
                return FileVisitResult.CONTINUE;
            }
        });

        return firstFile[0];
    }

    /**
     * 创建目录(如果不存在)
     *
     * @param directoryPath 目录路径
     * @throws IOException IO异常
     */
    public static void createDirectory(String directoryPath) throws IOException {
        Path path = Paths.get(directoryPath);
        if (!Files.exists(path)) {
            Files.createDirectories(path);
        }
    }

    /**
     * 生成一个十六位数的ID
     *
     * @return 十六位数的ID
     */
    private static String generateHexId() {
        UUID uuid = UUID.randomUUID();
        String uuidStr = uuid.toString().replace("-", ""); // 去掉UUID中的连字符
        return uuidStr.substring(0, 16); // 截取前16位
    }
}
相关推荐
YuanDaima20481 小时前
云计算基础与容器技术演进
java·服务器·人工智能·python·深度学习·云计算·个人开发
java1234_小锋1 小时前
SpringBoot可以同时处理多少请求?
java·spring boot·后端
JAVA面经实录9171 小时前
原码反码补码编码架构与进制底层设计思想
java·架构
wangl_921 小时前
初探 C# 15 的 Union Types
java·开发语言·算法·c#·.net·.net core
happymaker06261 小时前
Spring学习日记——DAY06(事务管理)
java·学习·spring
兰令水1 小时前
topcode【随机算法题】【2026.5.14打卡-java版本】
java·算法·leetcode
雪度娃娃1 小时前
结构型设计模式——代理模式
java·c++·设计模式·系统安全·代理模式
万邦科技Lafite1 小时前
京东商品详情 API 接口全面讲解
java·数据库·redis·api·电商开放平台
折哥的程序人生 · 物流技术专研1 小时前
Java面试85题图解版 · 全系列总目录
java·开发语言·后端·面试·职场和发展