Loading

SftpUtil(传输工具)

描述:在java项目中传输服务器数据到指定主机。

引入依赖

<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.55</version>
</dependency>

代码实现

/*
 * Copyright (c) 2005, 2024, EVECOM Technology Co.,Ltd. All rights reserved.
 * EVECOM PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package net.evecom.iaplatform.backup.util;

import cn.hutool.core.util.ObjectUtil;
import com.jcraft.jsch.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * <B>Description: 备份工具</B>
 * </P>
 * Revision Trail: (Date/Author/Description)
 * 2024/6/25 Ryan Huang CREATE
 *
 * @author Ryan Huang
 * @version 1.0
 */
public class SftpUtil {

    private static final Logger LOG = LoggerFactory.getLogger(SftpUtil.class);

    private String username;
    private String password;
    private String host;
    private Integer port;
    private Session session;
    private ChannelSftp sftpChannel;

    public SftpUtil(String host, Integer port, String username, String password) {
        this.host = host;
        this.port = port;
        this.username = username;
        this.password = password;
        init();
    }

    private void init() {
        JSch jsch = new JSch();
        try {
            session = jsch.getSession(username, host, port);
            session.setPassword(password);
            session.setConfig("StrictHostKeyChecking", "no");
            session.connect();

            sftpChannel = (ChannelSftp) session.openChannel("sftp");
            sftpChannel.connect();
            LOG.info("SFTP连接成功");
        } catch (Exception e) {
            LOG.error("连接失败:{}", e.getMessage());
            throw new RuntimeException("无法建立SFTP连接:" + e.getMessage(), e);
        }
    }

    /**
     * 文件传输
     *
     * @param sourcePath 源目录
     * @param targetPath 目标目录
     * @param closeSource 传输完成后是否关闭资源
     */
    public void transfer(String sourcePath, String targetPath, boolean closeSource) {
        transfer(sourcePath, targetPath, null, closeSource);
    }

    /**
     * 文件传输
     *
     * @param sourcePath 源目录
     * @param targetPath 目标目录
     * @param ignoredPathList 忽略路径
     * @param closeSource 传输完成后是否关闭资源
     */
    public void transfer(String sourcePath, String targetPath, List<String> ignoredPathList, boolean closeSource) {
        if(sourcePath == null || targetPath == null) {
            throw new IllegalArgumentException("源路径和目标路径不能为空");
        }
        try {
            uploadDirectory(sourcePath, targetPath, ignoredPathList);
            LOG.info("文件传输成功");
        } catch (Exception e) {
            LOG.error("文件传输失败:{}", e.getMessage());
            throw new RuntimeException("文件传输失败:" + e.getMessage(), e);
        } finally {
            if(closeSource) {
                // 资源泄露防护
                destroy();
            }
        }
    }

    /**
     * 文件传输
     *
     * @param sourcePath 源目录
     * @param targetPath 目标目录
     * @param startTime 源目录文件开始时间
     * @param endTime 源目录文件结束时间
     * @param closeSource 传输完成后是否关闭资源
     */
    public void transfer(String sourcePath, String targetPath, LocalDateTime startTime, LocalDateTime endTime, boolean closeSource) {
        transfer(sourcePath, targetPath, startTime, endTime, null, closeSource);
    }

    public void transfer(String sourcePath, String targetPath, LocalDateTime startTime, LocalDateTime endTime, List<String> ignoredPathList, boolean closeSource) {
        if(sourcePath == null || targetPath == null) {
            throw new IllegalArgumentException("源路径和目标路径不能为空");
        }
        try {
            uploadDirectory(sourcePath, targetPath, startTime, endTime, ignoredPathList);
            LOG.info("文件传输成功, 传输文件时间段{}-{}", startTime, endTime);
        } catch (Exception e) {
            LOG.error("文件传输失败:{}", e.getMessage());
            throw new RuntimeException("文件传输失败:" + e.getMessage(), e);
        } finally {
            if(closeSource) {
                // 资源泄露防护
                destroy();
            }
        }
    }

    /**
     * 保存inputStream文件到远程
     *
     * @param inputStream 流
     * @param remotePath 远程路径
     * @param remoteFileName 文件名
     * @param closeSource 是否关闭资源
     */
    public void uploadInputStreamToRemote(InputStream inputStream, String remotePath, String remoteFileName, boolean closeSource) {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()){
            byte[] buffer = new byte[1024];
            int read;
            while ((read = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, read);
            }
            byte[] fileContent = byteArrayOutputStream.toByteArray();
            String dirPath = handlePath(remotePath, "");
            sftpChannel.cd(dirPath);
            OutputStream outputStream = sftpChannel.put(remoteFileName);
            outputStream.write(fileContent);
            outputStream.flush();
            outputStream.close();

        } catch (Exception e) {
            LOG.error("文件传输失败:{}", e.getMessage());
            throw new RuntimeException("文件传输失败:" + e.getMessage(), e);
        }finally {
            if(closeSource) {
                // 资源泄露防护
                destroy();
            }
        }
    }


    /**
     * 销毁连接
     */
    public void destroy() {
        if (sftpChannel != null) {
            try {
                sftpChannel.disconnect();
            } catch (Exception e) {
                LOG.error("关闭SFTP通道失败:", e);
            }
        }
        if (session != null) {
            session.disconnect();
        }
    }


    /**
     * 上传目录
     *
     * @param localDirPath 本地目录
     * @param remoteBasePath 远程目录
     */
    private  void uploadDirectory(String localDirPath, String remoteBasePath)  {
        uploadDirectory(localDirPath, remoteBasePath, null);
    }

    /**
     * 上传目录
     *
     * @param localDirPath 本地目录
     * @param remoteBasePath 远程目录
     * @param ignoredFileList 忽略文件
     */
    private  void uploadDirectory(String localDirPath, String remoteBasePath, List<String> ignoredFileList)  {
        File localDir = new File(localDirPath);
        if (!localDir.exists() || !localDir.isDirectory()) {
            throw new IllegalArgumentException("Local path: " + localDirPath + " is not a valid directory.");
        }

        try {
            String remotePath = handlePath(remoteBasePath, localDir.getName());
            //目录不存在创建目录
            createRemoteDirectoriesRecursively(remotePath);

            sftpChannel.cd(remotePath);

            File[] files = localDir.listFiles();
            if (files != null) {
                if(ObjectUtil.isNotEmpty(ignoredFileList)){
                    files = Arrays.stream(files).filter(file -> !ignoredFileList.contains(file.getAbsolutePath())).toArray(File[]::new);
                }
                for (File file : files) {
                    if (file.isDirectory()) {
                        uploadDirectory(file.getAbsolutePath(), remotePath, ignoredFileList);
                    } else {
                        String remoteFilePath = handlePath(remotePath, file.getName());
                        sftpChannel.put(file.getAbsolutePath(), remoteFilePath);
                    }
                }
            }
        } catch (SftpException e) {
            throw new RuntimeException("Error during SFTP operation: " + e.getMessage(), e);
        }
    }

    /**
     * 上传目录
     *
     * @param localDirPath 本地目录
     * @param remoteBasePath 远程目录
     * @param startTime 文件开始时间
     * @param endTime 文件结束时间
     */
    private  void uploadDirectory(String localDirPath, String remoteBasePath, LocalDateTime startTime, LocalDateTime endTime)  {
        uploadDirectory(localDirPath, remoteBasePath, startTime, endTime, null);
    }

    /**
     * 上传目录
     *
     * @param localDirPath 本地目录
     * @param remoteBasePath 远程目录
     * @param startTime 文件开始时间
     * @param endTime 文件结束时间
     * @param ignoredPathList 忽略文件
     */
    private  void uploadDirectory(String localDirPath, String remoteBasePath, LocalDateTime startTime, LocalDateTime endTime, List<String> ignoredPathList)  {
        File localDir = new File(localDirPath);
        if (!localDir.exists() || !localDir.isDirectory()) {
            throw new IllegalArgumentException("Local path: " + localDirPath + " is not a valid directory.");
        }

        try {
            String remotePath = handlePath(remoteBasePath, localDir.getName());
            //目录不存在创建目录
            createRemoteDirectoriesRecursively(remotePath);

            sftpChannel.cd(remotePath);

            File[] files = localDir.listFiles((dir1, name) -> {
                File file = new File(dir1, name);
                ZoneId zoneId = ZoneId.systemDefault();
                if (startTime != null && endTime != null) {
                    return file.lastModified() >= startTime.atZone(zoneId).toInstant().toEpochMilli()
                            && file.lastModified() <= endTime.atZone(zoneId).toInstant().toEpochMilli();
                }else if (startTime != null) {
                    return file.lastModified() >= startTime.atZone(zoneId).toInstant().toEpochMilli();
                }
                return file.lastModified() <= endTime.atZone(zoneId).toInstant().toEpochMilli();
            });
            if (files != null) {
                if(ObjectUtil.isNotEmpty(ignoredPathList)){
                    files = Arrays.stream(files).filter(file -> !ignoredPathList.contains(file.getAbsolutePath())).toArray(File[]::new);
                }
                for (File file : files) {
                    if (file.isDirectory()) {
                        uploadDirectory(file.getAbsolutePath(), remotePath, startTime, endTime, ignoredPathList);
                    } else {
                        String remoteFilePath = handlePath(remotePath, file.getName());
                        sftpChannel.put(file.getAbsolutePath(), remoteFilePath);
                    }
                }
            }
        } catch (SftpException e) {
            throw new RuntimeException("Error during SFTP operation: " + e.getMessage(), e);
        }
    }

    /**
     * 上传文件
     *
     * @param localDirPath 本地路径
     * @param remoteBasePath 远程路径
     */
    public void uploadFile(String localDirPath, String remoteBasePath)  {
        File file = new File(localDirPath);
        if (!file.exists() || !file.isFile()) {
            throw new IllegalArgumentException("Local path: " + localDirPath + " is not a valid file.");
        }

        try{
            String remotePath = handlePath(remoteBasePath, "");
            //目录不存在创建目录
            createRemoteDirectoriesRecursively(remotePath);

            sftpChannel.cd(remotePath);

            String remoteFilePath = handlePath(remoteBasePath, file.getName());
            sftpChannel.put(localDirPath, remoteFilePath);
        } catch (SftpException e) {
            throw new RuntimeException("Error during SFTP operation: " + e.getMessage(), e);
        }
    }

    /**
     * 执行远程windows主机命令
     *
     * @param cmd 命令
     * @throws JSchException 异常
     * @throws IOException 异常
     */
    public int executeCmdForWindow(String cmd) throws JSchException, IOException {
        Channel channel = session.openChannel("exec");
        LOG.info("执行命令:" + cmd);
        ((ChannelExec) channel).setCommand("cmd.exe /c " + cmd);

        // 获取输出流和错误流以便读取命令执行结果
        InputStream in = channel.getInputStream();
        InputStream err = ((ChannelExec) channel).getErrStream();

        channel.connect();
        StringBuilder outputSb = new StringBuilder();
        byte[] buffer = new byte[1024];
        int status = 0;
        while (true) {
            while (in.available() > 0) {
                int i = in.read(buffer, 0, 1024);
                if (i < 0)
                    break;
                outputSb.append(new String(buffer, 0, i));
            }
            while (err.available() > 0) {
                int i = err.read(buffer, 0, 1024);
                if (i < 0)
                    break;
                outputSb.append(new String(buffer, 0, i));
            }
            if (channel.isClosed()) {
                if (in.available() > 0) continue;
                status = channel.getExitStatus();
                break;
            }
        }
        LOG.info("执行结果:" + outputSb);
        channel.disconnect();

        return status;
    }

    /**
     * 执行远程linux主机命令
     *
     * @param cmd 命令
     * @throws JSchException 异常
     * @throws IOException 异常
     */
    public int executeCmdForLinux(String cmd) throws JSchException, IOException {
        Channel channel = session.openChannel("shell");
        // 获取输入流和输出流以交互
        OutputStream input = channel.getOutputStream();
        PrintStream ps = new PrintStream(input, true);

        channel.connect();
        LOG.info("执行命令:" + cmd);
        ps.println(cmd + "; echo $?"); //添加 "; echo $?" 来获取命令的退出状态码

        BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
        String line;
        StringBuilder outputBuilder = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            outputBuilder.append(line).append("\n");
            // 当读取到退出状态码时,跳出循环
            if (line.trim().matches("\\d+")) {
                break;
            }
        }
        String[] outArray = outputBuilder.toString().trim().split("\n");
        int exitStatus = Integer.parseInt(outArray[outArray.length - 1]);

        // 判断命令是否成功执行
        if (exitStatus == 0) {
            LOG.info("命令执行成功");
        } else {
            LOG.error("命令执行失败,退出状态码: " + outputBuilder.toString());
        }

        reader.close();
        ps.close();
        channel.disconnect();

        return exitStatus;
    }
    /**
     * 判断远程目录是否存在
     *
     * @param remotePath 远程目录
     * @return true:存在 false:不存在
     */
    public boolean remoteDirectoryExists(String remotePath) {
        try {
            String handleFilePath = handlePath(remotePath, "");
            SftpATTRS attrs = sftpChannel.lstat(handleFilePath);
            return attrs != null && attrs.isDir();
        } catch (SftpException e) {
            if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                return false;
            } else {
                throw new RuntimeException("Error checking remote directory existence: " + e.getMessage(), e);
            }
        }
    }

    /**
     * 判断远程文件是否存在
     *
     * @param filePath 远程文件
     * @return true:存在 false:不存在
     */
    public boolean remoteFileExists(String filePath) {
        try {
            String handleFilePath = handlePath(filePath, "");
            if (handleFilePath.endsWith("/")) {
                handleFilePath = handleFilePath.substring(0, handleFilePath.length() - 1);
            }
            SftpATTRS attrs = sftpChannel.stat(handleFilePath);
            return attrs != null && !attrs.isDir();
        } catch (SftpException e) {
            if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                return false;
            } else {
                throw new RuntimeException("Error checking remote File existence: " + e.getMessage(), e);
            }
        }
    }

    /**
     * 判断远程路径是否为目录
     *
     * @param path 路径
     */
    public boolean remotePathIsDir(String path){
        try {
            String handleFilePath = handlePath(path, "");
            if (handleFilePath.endsWith("/")) {
                handleFilePath = handleFilePath.substring(0, handleFilePath.length() - 1);
            }
            SftpATTRS attrs = sftpChannel.stat(handleFilePath);
            if (attrs != null) {
                if (attrs.isDir()) {
                    return true;
                }
            }
        } catch (SftpException e) {
            throw new RuntimeException("Error checking remote path existence: " + e.getMessage(), e);
        }
        return false;
    }
    /**
     * 路径处理
     *
     * @param path 路径
     * @param fileName 文件名
     */
    public static String handlePath(String path, String fileName) {
        String str;
        str = path.replace("\\", "/");
        if (str.endsWith("/")) {
            str = str + fileName;
        } else {
            str = str + "/" + fileName;
        }

        if (path.charAt(0) != '/') {
            str = "/" + str;
        }
        return str;
    }

    /**
     * 递归创建目录
     *
     * @param remotePath 目录
     * @throws SftpException 异常
     */
    private void createRemoteDirectoriesRecursively(String remotePath) throws SftpException {
        // 分割路径以获取各级目录名
        String[] directories = remotePath.split("/");

        // 过滤掉空字符串,因为split处理绝对路径时会包含一个空元素
        List<String> validDirectories = Arrays.stream(directories)
                .filter(dir -> !dir.isEmpty())
                .collect(Collectors.toList());

        // 用于构建当前路径的StringBuilder
        StringBuilder currentPath = new StringBuilder();

        //在开头添加/代表从跟节点开始创建目录
        currentPath.append("/");
        // 遍历所有层级的目录名
        for (String dir : validDirectories) {

            // 添加目录名到当前路径,并以"/"分隔
            currentPath.append(dir).append("/");

            //:则代表磁盘路径,跳过
            if(dir.contains(":")) continue;

            // 构建完整的路径并检查是否存在
            String fullPath = currentPath.toString();
            if (!remoteDirectoryExists(fullPath)) {
                // 如果不存在,则创建该层级的目录
                sftpChannel.mkdir(fullPath);
            }
        }
    }

}

posted @ 2024-07-02 17:42  IamHzc  阅读(13)  评论(0编辑  收藏  举报