SFTP上传、下载(递归下载)、删除(递归删除)之Java工具类

目录

准备工作

工具类

使用示例

        上传(示例)

        下载文件(示例)

        下载文件夹(示例)

        递归下载文件夹(示例)

        删除文件(示例)

        删除空的文件夹(示例)

        递归删除文件夹(示例)


封装了一个工具类,个人感觉挺好用!开心~


准备工作:在pom.xml中引入相关依赖

<!-- https://mvnrepository.com/artifact/com.jcraft/jsch -->
<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.55</version>
</dependency>

工具类:

声明:此工具类看上去挺长,实际对外只提供了upload、download、delete这三个方法,其余方法均为
           内部协助方法。

import com.jcraft.jsch.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

/**
 * SFTP工具类
 *
 * @author JustryDeng
 * @date 2019/3/15 12:33
 */
public class SftpUtil {

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

    private static final String NODE_SEPERATOR = "/";

    /** 未调用初始化方法 错误码 */
    private static final int DONOT_INIT_ERROR_CODE = 19940205;

    /** 未调用初始化方法 错误提示信息 */
    private static final String DONOT_INIT_ERROR_MSG = "please invoke init(...) first!";

    private static Session session;

    private static Channel channel;

    private static ChannelSftp channelSftp;

    /**
     * 下载时, 释放资源的标识, 当其值为 0 时, 则调用close()
     * 注:当递归下载时,我们希望 递归内部不进行close(),只有最外层的方法才进行close()
     */
    private static int recursiveDownloadCloseFlag = 0;

    /**
     * 初始化
     *
     * @param ip
     *            sftp地址
     * @param port
     *            sftp端口
     * @param username
     *            用户名
     * @param password
     *            密码
     * @throws JSchException JSch异常
     * @date 2019/3/15 12:41
     */
    public static void init(String ip, Integer port, String username, String password) throws JSchException {
        JSch jsch = new JSch();
        jsch.getSession(username, ip, port);
        session = jsch.getSession(username, ip, port);
        session.setPassword(password);
        Properties sshConfig = new Properties();
        sshConfig.put("StrictHostKeyChecking", "no");
        session.setConfig(sshConfig);
        session.connect();
        logger.info("Session connected!");
        channel = session.openChannel("sftp");
        channel.connect();
        logger.info("Channel connected!");
        channelSftp = (ChannelSftp) channel;
    }

    /**
     * 上传
     *
     * @param remoteDir
     *            要上传到sftp上哪一个文件夹  【绝对路径】,如:/files/test/
     * @param src
     *            要上传的文件的位置         【绝对路径】,如: /var/abc.txt
     *
     * @throws SftpException 当未初始化就调用此方法时,会出现异常;当sftp上传出错时会抛出SftpException
     * @date 2019/3/15 12:44
     */
    public static void upload(String remoteDir, String src)
            throws SftpException {
        if (channelSftp == null) {
            throw new SftpException(DONOT_INIT_ERROR_CODE, DONOT_INIT_ERROR_MSG);
        }
        remoteDir = handllePath(remoteDir, true, true);
        String fileName = src.substring(src.lastIndexOf(NODE_SEPERATOR) + 1);
        try {
            // 确保文件夹存在
            pMkdirSftp(remoteDir);
            // 执行上传
            channelSftp.put(src,remoteDir + fileName);
            logger.info("upload FROM 【{}】 TO 【{}】 success!", src, remoteDir + fileName);
        } finally {
            close();
        }
    }

    /**
     * 文件下载
     *
     * @param remoteDirOrRemoteFile
     *             要下载的sftp上的文件 或 sftp上的文件夹 【绝对路径】
     *
     * @param localDir
     *             用于存放下载的文件的本地文件夹 【绝对路径】
     *             注:可以不存在,会自动创建
     *             注:可以不存在,会自动创建
     *
     * @param recursiveDownload
     *             当remoteDirOrRemoteFile指向文件夹时,此参数生效;
     *             为false时,
     *                  只下载remoteDirOrRemoteFile文件夹下的所有文件,remoteDirOrRemoteFile下的所
     *                  有文件夹(及里面的内容)将会被忽略
     *              为true时,
     *                      下载remoteDirOrRemoteFile文件夹下的所有文件、文件夹(及里面的内容)
     *
     * @return 下载到本地的文件的 绝对路径名  集合   如: /var/data/down/meinv.png
     * @throws SftpException 当未初始化就调用此方法是,会出现异常;当sftp下载出错时会抛出SftpException
     * @date 2019/3/15 12:44
     */
    public static List<String> download(String remoteDirOrRemoteFile, String localDir, boolean recursiveDownload)
            throws SftpException {
        if (channelSftp == null) {
            throw new SftpException(DONOT_INIT_ERROR_CODE, DONOT_INIT_ERROR_MSG);
        }
        // 保证 localDir 以 “/” 结尾
        localDir = handllePath(localDir, false, true);
        // 确保本地文件夹存在
        pMkdirLocal(localDir);
        List<String> fileNameList = new ArrayList<>();
        String localAbsoluteFilename;
        String sftpAbsoluteFilename;
        try {
            if (isDirectory(remoteDirOrRemoteFile)) {
                // 保证 remoteDirOrRemoteFile 以 “/” 开头,以 “/” 结尾
                remoteDirOrRemoteFile = handllePath(remoteDirOrRemoteFile, true, true);
                Vector<?> vector = channelSftp.ls(remoteDirOrRemoteFile);
                String fileName;
                // 列出文件名
                for (Object item : vector) {
                    ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) item;
                    fileName = entry.getFilename();
                    if (invalidFileName(fileName)) {
                        continue;
                    }
                    sftpAbsoluteFilename = remoteDirOrRemoteFile + fileName;
                    localAbsoluteFilename = localDir + fileName;
                    if (!isDirectory(sftpAbsoluteFilename)) {
                        channelSftp.get(sftpAbsoluteFilename, localAbsoluteFilename);
                        fileNameList.add(localAbsoluteFilename);
                        logger.info("Downloaded file FROM 【{}】 TO 【{}】 ", sftpAbsoluteFilename, localAbsoluteFilename);
                        continue;
                    }
                    // 下载文件夹
                    if (recursiveDownload) {
                        recursiveDownloadCloseFlag++;
                        download(sftpAbsoluteFilename, localAbsoluteFilename, true);
                        recursiveDownloadCloseFlag--;
                    }
                }
            } else {
                // 保证 remoteDirOrRemoteFile 以 “/” 开头
                remoteDirOrRemoteFile = handllePath(remoteDirOrRemoteFile, true, false);
                localAbsoluteFilename = localDir + getFilenameFromPath(remoteDirOrRemoteFile);
                channelSftp.get(remoteDirOrRemoteFile, localAbsoluteFilename);
                fileNameList.add(localAbsoluteFilename);
                logger.info("Downloaded file FROM 【{}】 TO 【{}】 ", remoteDirOrRemoteFile, localAbsoluteFilename);
            }
        } finally {
            if (recursiveDownloadCloseFlag == 0) {
                close();
            }
        }
        return fileNameList;
    }


    /**
     * 删除文件 或 删除文件夹
     * 注: 如果是文件夹, 不论该文件夹中有无内容,都能删除, 因此:此方法慎用
     *
     * @param remoteDirOrRemoteFile
     *            要删除的文件  或 文件夹
     * @throws SftpException
     *             当未初始化就调用此方法是,会出现异常;当sftp删除出错时会抛出SftpException
     * @date 2019/3/17 11:33
     */
    public static void delete(String remoteDirOrRemoteFile) throws SftpException {
        if (channelSftp == null) {
            throw new SftpException(DONOT_INIT_ERROR_CODE, DONOT_INIT_ERROR_MSG);
        }
        List<String> targetFileOrDirContainer = new ArrayList<>(8);
        targetFileOrDirContainer.add(remoteDirOrRemoteFile);
        List<String> toBeDeletedEmptyDirContainer = new ArrayList<>(8);
        if (isDirectory(remoteDirOrRemoteFile)) {
            toBeDeletedEmptyDirContainer.add(remoteDirOrRemoteFile);
        }
        try {
            collectToBeDeletedEmptyDir(toBeDeletedEmptyDirContainer, targetFileOrDirContainer);
            if (!toBeDeletedEmptyDirContainer.isEmpty()) {
                String targetDir;
                for (int i = toBeDeletedEmptyDirContainer.size() - 1; i >= 0; i--) {
                    targetDir = toBeDeletedEmptyDirContainer.get(i);
                    channelSftp.rmdir(targetDir);
                    logger.debug("rmdir 【{}】 success!", targetDir);
                }
            }
        } finally {
            close();
        }
    }

    /**
     * 删除相关文件 并 采集所有 需要被删除的 文件夹
     *
     * 注: 如果是文件夹, 不论该文件夹中有无内容,都能删除, 因此:此方法慎用
     *
     * @param toBeDeletedEmptyDirContainer
     *            所有待删除的空文件夹集合
     *
     * @param targetFileOrDirContainer
     *            本次, 要删除的文件的集合   或   本次, 要删除的文件所在文件夹的集合
     *
     * @throws SftpException 当未初始化就调用此方法是,会出现异常;当sftp删除出错时会抛出SftpException
     * @date 2019/3/17 11:33
     */
    private static void collectToBeDeletedEmptyDir(List<String> toBeDeletedEmptyDirContainer,
                                            List<String> targetFileOrDirContainer)
            throws SftpException {
        List<String> todoCallDirContainer = new ArrayList<>(8);
        List<String> subfolderList;
        for (String remoteDirOrRemoteFile : targetFileOrDirContainer) {
            subfolderList = fileDeleteExecutor(remoteDirOrRemoteFile);
            toBeDeletedEmptyDirContainer.addAll(subfolderList);
            todoCallDirContainer.addAll(subfolderList);
        }
        if (!todoCallDirContainer.isEmpty()) {
            collectToBeDeletedEmptyDir(toBeDeletedEmptyDirContainer, todoCallDirContainer);
        }
    }

    /**
     * 删除remoteDirOrRemoteFile指向的文件 或 删除remoteDirOrRemoteFile指向的文件夹下的所有子级文件
     * 注: 如果是文件夹, 只会删除该文件夹下的子级文件;不会删除该文件夹下的孙子级文件(如果有孙子级文件的话)
     *
     * @param remoteDirOrRemoteFile
     *            要删除的文件 或 要 文件夹   【绝对路径】
     *
     * @return remoteDirOrRemoteFile指向的文件夹 下的 文件夹集合
     *             注: 如果remoteDirOrRemoteFile指向的是文件的话,返回空的集合
     *             注: 只会包含子级文件夹,不包含孙子级文件夹(如果有孙子级文件夹的话)
     *
     * @throws SftpException 当未初始化就调用此方法是,会出现异常;当sftp删除出错时会抛出SftpException
     * @date 2019/3/15 12:44
     */
    private static List<String> fileDeleteExecutor (String remoteDirOrRemoteFile) throws SftpException {
        List<String> subfolderList = new ArrayList<>(8);
        // 如果是文件,直接删除
        if (!isDirectory(remoteDirOrRemoteFile)) {
            channelSftp.rm(remoteDirOrRemoteFile);
            logger.debug("rm 【{}】 success!", remoteDirOrRemoteFile);
            return subfolderList;
        }
        // 保证 remoteDirOrRemoteFile 以 “/” 开头,以 “/” 结尾
        remoteDirOrRemoteFile = handllePath(remoteDirOrRemoteFile, true, true);
        Vector<?> vector = channelSftp.ls(remoteDirOrRemoteFile);
        String fileName;
        String sftpAbsoluteFilename;
        // 列出文件名
        for (Object item : vector) {
            ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) item;
            fileName = entry.getFilename();
            if (invalidFileName(fileName)) {
                continue;
            }
            sftpAbsoluteFilename = remoteDirOrRemoteFile + fileName;
            // 如果是文件,直接删除
            if (!isDirectory(sftpAbsoluteFilename)) {
                channelSftp.rm(sftpAbsoluteFilename);
                logger.debug("rm 【{}】 success!", sftpAbsoluteFilename);
                continue;
            }
            subfolderList.add(sftpAbsoluteFilename);
        }
        return subfolderList;
    }

    /**
     * 判断SFTP上的path是否为文件夹
     * 注:如果该路径不存在,那么会返回false
     *
     * @param path
     *            SFTP上的路径
     * @return 判断结果
     * @throws SftpException 当path不存在时抛出SftpException异常
     * @date 2019/3/15 17:57
     */
    private static boolean isDirectory(String path) throws SftpException {
        // 合法的错误id
        int legalErrorId = 4;
        try {
            channelSftp.cd(path);
            return true;
        } catch (SftpException e) {
            logger.debug(" channelSftp.cd() fail!  message -> {}, id -> {}", e.getMessage(), e.id);
            // 如果 path不存在,那么报错信息为【No such file】,错误id为【2】
            // 如果 path存在,但是不能cd进去,那么报错信息形如【Can't change directory: /files/sqljdbc4-3.0.jar】,错误id为【4】
            if (legalErrorId == e.id) {
                return false;
            }
            throw new SftpException(e.id, e.getMessage(), e);
        }
    }

    /**
     * 从给定路径中截取文件名
     *
     * @param path
     *            路径,  如: /files/abc/info.yml
     * @return  文件名, 如: info.yml
     * @date 2019/3/15 17:36
     */
    private static String getFilenameFromPath(String path){
        return path.substring(path.lastIndexOf(NODE_SEPERATOR) + 1);
    }

    /**
     * 判断是否为无效的文件名
     * 注:文件(夹)名为【.】或【..】时,是无效的
     *
     * @param fileName
     *            文件名
     * @return  是有无效
     * @date 2019/3/15 17:06
     */
    private static boolean invalidFileName(String fileName) {
        return ".".equals(fileName) || "..".equals(fileName);
    }

    /**
     * (在本地上)创建文件夹
     * 注:如果要创建的文件夹的长辈级目录不存在,那么会进行相应的创建
     *
     * @param dirPath
     *            文件夹 全路径名 , 如: /var/data/excel/
     * @date 2019/3/15 15:30
     */
    private static void pMkdirLocal (String dirPath) {
        File file = new File(dirPath);
        if(!file.exists()){
            boolean result = file.mkdirs();
            logger.debug("dir path 【{}】 is not exist, try to create it, result is -> {}", dirPath, result);
        }

    }

    /**
     * 路径处理器
     *
     * 根据参数控制处理类型,如:
     * 当: originPath 为【var/appps】时,
     * 当: handleHead 为 true, 处理结果为【/var/appps】
     * 当: handleTail 为 true, 处理结果为【var/appps/】
     * 当: handleHead 和 handleTail 均为 true, 处理结果为【/var/appps/】
     *
     * @param originPath
     *            要处理的路径
     * @param handleHead
     *            处理 起始处
     * @param handleTail
     *            处理 结尾处
     *
     * @return  处理后的路径
     * @date 2019/3/15 14:08
     */
    private static String handllePath(String originPath, boolean handleHead, boolean handleTail){
        if(originPath == null || "".equals(originPath.trim())) {
            return NODE_SEPERATOR;
        }
        if(handleHead && !originPath.startsWith(NODE_SEPERATOR)) {
            originPath = NODE_SEPERATOR.concat(originPath);
        }
        if (handleTail && !originPath.endsWith(NODE_SEPERATOR)) {
            originPath = originPath.concat(NODE_SEPERATOR);
        }
        return originPath;
    }

    /**
     * (在SFTP上)创建文件夹
     * 注:如果要创建的文件夹的长辈级目录不存在,那么会进行相应的创建
     *
     * @param dirPath
     *            文件夹 全路径名 , 如: /files/a/b/c/
     * @throws SftpException 创建文件夹异常
     * @date 2019/3/15 15:30
     */
    private static void pMkdirSftp (String dirPath) throws SftpException {
        dirPath = handllePath(dirPath, true, true);
        String[] nodeArray = dirPath.split(NODE_SEPERATOR);
        String currPath = NODE_SEPERATOR;
        for (String node : nodeArray) {
            if ("".equals(node.trim())) {
                continue;
            }
            try {
                currPath = currPath + node + NODE_SEPERATOR;
                channelSftp.cd(currPath);
            } catch (SftpException e) {
                // 文件夹不存在时
                channelSftp.mkdir(currPath);
                logger.debug("no such dir -> {} ; try to create it, result is success!", currPath);
            }
        }
    }

    /**
     * 释放资源
     *
     * @date 2019/3/15 12:47
     */
    private static void close() {
        if (channelSftp != null && channelSftp.isConnected()) {
                channelSftp.disconnect();
                channelSftp.exit();
        }
        if (channel != null && channel.isConnected()) {
                channel.disconnect();
        }
        if (session != null && session.isConnected()) {
                session.disconnect();
        }
        logger.info("exit sftp!");
    }

}

使用示例:

声明:下面示例的效果图(SFTP相关图)就不贴出来了,仅给出程序方面的示例图。

上传(示例):

控制台输出:

下载文件(示例):

控制台输出:

下载文件夹(示例):

控制台输出:

递归下载文件夹(示例):

控制台输出:

删除文件(示例):

控制台输出:

删除空的文件夹(示例):

控制台输出:

递归删除文件夹(示例):

控制台输出:

 

^_^ 如有不当之处,欢迎指正

^_^ 代码(工具类、以及测试代码)托管链接 
               
https://github.com/JustryDeng/CommonRepository

^_^ 本文已经被收录进《程序员成长笔记(第四部)》,笔者JustryDeng

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
您好!如果您想要使用Java进行SFTP操作,可以使用JSch库来实现。下面是一个示例的SFTP工具类,帮助您进行SFTP文件传输: ```java import com.jcraft.jsch.*; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Properties; public class SftpUtils { private String host; private int port; private String username; private String password; public SftpUtils(String host, int port, String username, String password) { this.host = host; this.port = port; this.username = username; this.password = password; } public void uploadFile(String localFilePath, String remoteFilePath) throws JSchException, SftpException { JSch jsch = new JSch(); Session session = jsch.getSession(username, host, port); session.setPassword(password); Properties config = new Properties(); config.put("StrictHostKeyChecking", "no"); session.setConfig(config); session.connect(); ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); channel.connect(); File localFile = new File(localFilePath); FileInputStream fis = new FileInputStream(localFile); channel.put(fis, remoteFilePath); fis.close(); channel.disconnect(); session.disconnect(); } public void downloadFile(String remoteFilePath, String localFilePath) throws JSchException, SftpException { JSch jsch = new JSch(); Session session = jsch.getSession(username, host, port); session.setPassword(password); Properties config = new Properties(); config.put("StrictHostKeyChecking", "no"); session.setConfig(config); session.connect(); ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); channel.connect(); File localFile = new File(localFilePath); FileOutputStream fos = new FileOutputStream(localFile); channel.get(remoteFilePath, fos); fos.close(); channel.disconnect(); session.disconnect(); } public static void main(String[] args) { String host = "your_sftp_host"; int port = 22; String username = "your_username"; String password = "your_password"; String localFilePath = "local_file_path"; String remoteFilePath = "remote_file_path"; SftpUtils sftpUtils = new SftpUtils(host, port, username, password); try { // 上传文件 sftpUtils.uploadFile(localFilePath, remoteFilePath); // 下载文件 // sftpUtils.downloadFile(remoteFilePath, localFilePath); } catch (JSchException | SftpException e) { e.printStackTrace(); } } } ``` 您可以根据实际情况修改主机地址、端口、用户名、密码,以及本地文件路径和远程文件路径,然后在`main`方法中调用相关方法进行SFTP文件上传下载。 请确保将JSch库添加到您的项目依赖中。希望对您有所帮助!如果有任何问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值