通用FTP上传下载文件或文件夹支持断点续传(带心跳keepalive)

前言:

FTP客户端实现要建立两个通道,一个控制命令通道,让FTP服务器知道客户端要干什么,一个数据传输通道。所谓的两个通道只不过是两个调用了connect函数的连接,只是控制命令通道专门用来传输一些字符串命令信息,而数据通道则是用来传输文件。控制命令通道一定是由客户端向服务器的连接(默认的端口是21,也可以指定端口,这要看服务器的设置)。连接的过程完成了FTP的登录。数据通道则不一定啦,具体哪个连哪个。

具体实现:

在pom.xml里的<dependencies>here</dependencies>在here里添加。

<dependency>
    <groupId>commons-net</groupId>
    <artifactId>commons-net</artifactId>
    <version>3.6</version>
</dependency>

FTP工具类

FTPUtils.java

由于篇幅过宽,建议粘贴到编辑器进行编辑。

package com.xkcoding.orm.mybatis.plus.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.util.StringUtils;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;


/**
 * @desc: demo
 * @name: FTPUtils.java
 * @author: andrew
 * @createTime: 2021年10月12日 下午10:43:27
 * @history:
 * @version: v1.0
 */

@Slf4j
public class FTPUtils {
    public FTPClient ftpClient = new FTPClient();

    public static String LOCAL_CHARSET = "GBK";


    public FTPUtils() {
        // 设置将过程中使用到的命令输出到控制台
        this.ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
    }
    /**
     *
     * @todo连接到FTP服务器
     * @param hostname 主机名
     * @param port     端口
     * @param username 用户名
     * @param password 密码
     * @return 是否连接成功
     * @throws IOException
     *
     */
    public boolean connect(String hostname, int port, String username, String password) throws IOException {
        ftpClient.connect(hostname, port);
        if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
            if (ftpClient.login(username, password)) {
                // 设置PassiveMode传输
                ftpClient.enterLocalPassiveMode();
                // 设置以二进制流的方式传输
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

                // 重要,每隔120S向控制端口发送心跳数据,保证控制端口的活性
                int defaultTimeOutSecond = 2*60*1000;

                /*    setDefaultTimeout
                *(设置一个超时时间,用来当这个 Socket 调用了 read() 从 InputStream 输入流中
                 *    读取数据的过程中,如果线程进入了阻塞状态,那么这次阻塞的过程耗费的时间如果
           *    超过了设置的超时时间,就会抛出一个 SocketTimeoutException 异常,但只是将
            *    线程从读数据这个过程中断掉,并不影响 Socket 的后续使用。
             *    如果超时时间为0,表示无限长。)
          *  (注意,并不是读取输入流的整个过程的超时时间,而仅仅是每一次进入阻塞等待输入流中
           *    有数据可读的超时时间)
                 */
                ftpClient.setDefaultTimeout(defaultTimeOutSecond);
                //设置默认超时时间setDefaultTimeout() 超时的工作跟 setSoTimeout() 是相同的,区别仅在于后者会覆盖掉前者设置的值。

                /**
                 * 使用指定的超时值将此套接字连接到服务器。
                 * 零超时被解释为无限超时。连接然后将阻塞,直到建立或发生错误
                 */
                ftpClient.setConnectTimeout(defaultTimeOutSecond);//设置连接超时时间

                /**
                 * 将此选项设置为非零
                 * 超时,调用此ServerSocket的accept()
                 * 将仅在这段时间内阻塞。如果超时过期,
                 * 虽然ServerSocket仍然有效。必须启用该选项
                 * 在进入闭锁操作之前,应使其生效。这个超时必须是大于0
                 * 零超时被解释为无限超时。
                 */
                ftpClient.setDataTimeout(defaultTimeOutSecond);//设置读取数据超时时间(每次socket传输,不是指整个下载)

                this.ftpClient.setControlKeepAliveTimeout(15);

                // 设置控制端口发送心跳数据时控制端口的响应超时,得到回复结束后恢复原来时长
                this.ftpClient.setControlKeepAliveReplyTimeout(5000);
                if (FTPReply.isPositiveCompletion(ftpClient.sendCommand(
                        "OPTS UTF8", "ON"))) {// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK).
                    LOCAL_CHARSET = "UTF-8";
                }
                System.out.println("FTP编码为"+LOCAL_CHARSET);
                ftpClient.setControlEncoding(LOCAL_CHARSET);
                String root = ftpClient.printWorkingDirectory();
                if (!root.equals("/")){
                    System.out.println("FTP登录后的服务器根目录为不是/,请修改FTP根目录,不然导致上远程目录的绝对路径的都会报错");
                    return false;
                }else {
                    System.out.println("服务器根目录为/");
                }
                return true;
            }
        }
        disconnect();
        return false;
    }

    /**
     *
     * @todo连接到FTP服务器
     * @param hostname 主机名
     * @param port     端口
     * @return 是否连接成功
     * @throws IOException
     *
     */
    public boolean connect(String hostname, int port) throws IOException {
        ftpClient.connect(hostname, port);
//        ftpClient.setControlEncoding("GBK");
        if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
            ftpClient.login("anonymous", "");
            ftpClient.enterLocalPassiveMode();
            // 设置以二进制流的方式传输
            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

            // 重要,每隔120S向控制端口发送心跳数据,保证控制端口的活性
            int defaultTimeOutSecond = 2*60*1000;

            /*    setDefaultTimeout
             *(设置一个超时时间,用来当这个 Socket 调用了 read() 从 InputStream 输入流中
             *    读取数据的过程中,如果线程进入了阻塞状态,那么这次阻塞的过程耗费的时间如果
             *    超过了设置的超时时间,就会抛出一个 SocketTimeoutException 异常,但只是将
             *    线程从读数据这个过程中断掉,并不影响 Socket 的后续使用。
             *    如果超时时间为0,表示无限长。)
             *  (注意,并不是读取输入流的整个过程的超时时间,而仅仅是每一次进入阻塞等待输入流中
             *    有数据可读的超时时间)
             */
            ftpClient.setDefaultTimeout(defaultTimeOutSecond);//设置默认超时时间

            /**
             * 使用指定的超时值将此套接字连接到服务器。
             * 零超时被解释为无限超时。连接然后将阻塞,直到建立或发生错误
             */
            ftpClient.setConnectTimeout(defaultTimeOutSecond);//设置连接超时时间

            /**
             * 将此选项设置为非零
             * 超时,调用此ServerSocket的accept()
             * 将仅在这段时间内阻塞。如果超时过期,
             * 虽然ServerSocket仍然有效。必须启用该选项
             * 在进入闭锁操作之前,应使其生效。这个超时必须是大于0
             * 零超时被解释为无限超时。
             */
            ftpClient.setDataTimeout(defaultTimeOutSecond);//设置读取数据超时时间(每次socket传输,不是指整个下载)

            this.ftpClient.setControlKeepAliveTimeout(15);


            // 设置控制端口发送心跳数据时控制端口的响应超时,得到回复结束后恢复原来时长
            this.ftpClient.setControlKeepAliveReplyTimeout(5000);
            if (FTPReply.isPositiveCompletion(ftpClient.sendCommand(
                    "OPTS UTF8", "ON"))) {// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK).
                LOCAL_CHARSET = "UTF-8";
            }
            System.out.println("FTP编码为"+LOCAL_CHARSET);
            ftpClient.setControlEncoding(LOCAL_CHARSET);
            String root = ftpClient.printWorkingDirectory();
            if (!root.equals("/")){
                System.out.println("FTP登录后的服务器根目录为不是/,请修改FTP根目录,不然导致上远程目录的绝对路径的都会报错");
                return false;
            }else {
                System.out.println("服务器根目录为/");
            }

                return true;
            }

        disconnect();
        return false;
    }


    /**
     * 从FTP服务器上下载文件,支持断点续传,上传百分比汇报
     * @param remotePaths 远程文件或文件夹路径
     * @param local  本地目录路径
     * @return 上传的状态
     */
    public String download(String local, String... remotePaths) throws Throwable {
        if (StringUtils.isEmpty(local) || remotePaths == null || remotePaths.length==0){
            log.error("本地文件为空");
            return "本地文件为空";
        }

        // 检查远程文件是否存在
        for (String remote:remotePaths){
            remote = FTPPathToolkit.formatPathFTP(remote);
        FTPFile[] files = ftpClient.listFiles(new String(remote.getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
        if (files.length < 1) {
            System.out.println("远程文件不存在");
            return "Remote_File_Noexist";
        }
        File folder = new File(local);
        if (!folder.exists()) { // 如果本地文件夹不存在,则创建
            folder.mkdirs();
        }
        String result = "";
        if(getFileType(remote)==1){   //是目录
          result = downloadFolder(getURL(remote),local);
          System.out.println(result);
        }else {
            String localPath = null;
            localPath = local + "\\" + files[0].getName();
            localPath = FTPPathToolkit.formatPathFile(localPath);
            File file = null;   //本地文件  = new File(localPath)
            File[] listFiles = folder.listFiles();
            boolean flag = false;
            for (File fileTemp : listFiles) {
                if (fileTemp.getName().equals(files[0].getName())) {
                    flag = true;
                    file = fileTemp;
                    break;
                }
            }
            long lRemoteSize = files[0].getSize();
            // 本地存在文件,进行断点下载
            if (flag) {
                long localSize = file.length();
                // 判断本地文件大小是否大于远程文件大小
                if (localSize >= lRemoteSize) {
                    System.out.println("本地文件大于远程文件,下载中止");
                    return "Local_Bigger_Remote";
                }
                result = downloadFile(remote, file, localSize, lRemoteSize); //断点续传
                System.out.println(result);
            } else {
                System.out.println("从0下载");
                file = new File(localPath);
                result = downloadFile(remote, file, 0L, lRemoteSize);
                System.out.println(result);
            }
        }
        }
        return "OK";
    }

   public String downloadFolder(URL url,String localDir) throws Throwable {
       String path = url.getPath();
       File folder = new File(localDir + "/" + new File(path).getName());
       if (!folder.exists()) {
           folder.mkdirs();
       }
       String result = "";
       localDir = folder.getAbsolutePath();
       FTPFile[] ftpFiles = ftpClient.listFiles(new String(path.getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
       String name = null;
       for (FTPFile file : ftpFiles) {
           name = file.getName();
           // 排除隐藏目录
           if (".".equals(name) || "..".equals(name)) {
               continue;
           }
           if (file.getType()==FTPFile.DIRECTORY_TYPE) { // 递归下载子目录
               result = downloadFolder(getURL(url, file.getName()), localDir);
           } else if (file.getType() == FTPFile.FILE_TYPE) { // 下载文件
               File localFolder = new File(localDir);
               File file1 = null;
               File[] localfiles = localFolder.listFiles();
               boolean flag=false;
               long lRemoteSize = file.getSize();
               for (File fileTemp:localfiles) {
                   if (fileTemp.getName().equals(file.getName())) {
                       flag = true;
                       file1 = fileTemp;
                       break;
                   }
               }
               if (flag){
                   System.out.println("断点续传");
                   long local_file_size=file1.length();
                   System.out.println("从"+local_file_size+"开始");
                   result =   downloadFile(FTPPathToolkit.formatPathFTP(url.getPath()+"/"+name),file1,local_file_size,lRemoteSize);
               }else {
                   System.out.println("从0下载");
                   file1 = new File(localDir + "/" + name);
                   result = downloadFile(FTPPathToolkit.formatPathFTP(url.getPath()+"/"+name),file1,0L,lRemoteSize);
               }

           }
       }
     return  result;
   }

    public String downloadFile(String remoteFile,File localFile,long localSize,long lRemoteSize) throws Throwable {
        if (localSize>0){
            OutputStream out = null;
            InputStream in = null;
            try{
            // 进行断点续传,并记录状态
            out = new FileOutputStream(localFile, true);
            ftpClient.setRestartOffset(localSize);
            in = ftpClient.retrieveFileStream(new String(remoteFile.getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
            byte[] bytes = new byte[1024];
            long step = lRemoteSize / 100;
            long process = localSize / step;
            int c;
            while ((c = in.read(bytes)) != -1) {
                out.write(bytes, 0, c);
                localSize += c;
                long nowProcess = localSize / step;
                if (nowProcess > process) {
                    process = nowProcess;
                    if (process % 10 == 0)
                        System.out.println("下载进度:" + process);
                    // TODO 更新文件下载进度,值存放在process变量中
                }
            }
            in.close();
            out.close();
            boolean isDo = ftpClient.completePendingCommand();
            if (isDo) {
                return  "Download_From_Break_Success";
            } else {
                return  "Download_From_Break_Failed";
            }
            }catch (Throwable throwable){
                if (in!=null){
                    in.close();
                }
                if (out!=null){
                    out.close();
                }
                throw new Throwable();
            }
        }else {
            System.out.println(localFile.getPath());
            System.out.println(localFile.getName());
            OutputStream out = null;
            InputStream in = null;
            try {
                out = new FileOutputStream(localFile);
                in = ftpClient.retrieveFileStream(new String(remoteFile.getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
                byte[] bytes = new byte[1024];
                long step = lRemoteSize / 100;
                long process = 0;
                localSize = 0L;
                int c;
                while ((c = in.read(bytes)) != -1) {
                    out.write(bytes, 0, c);
                    localSize += c;
                    long nowProcess = localSize / step;
                    if (nowProcess > process) {
                        process = nowProcess;
                        if (process % 10 == 0)
                            System.out.println("下载进度:" + process);
                        // TODO 更新文件下载进度,值存放在process变量中
                    }
                }
                in.close();
                out.close();
                boolean upNewStatus = ftpClient.completePendingCommand();
                if (upNewStatus) {
                    return "Download_New_Success";
                } else {
                    return "Download_New_Failed";
                }
            }catch (Throwable throwable){
                if (in!=null){
                    in.close();
                }
                if (out!=null){
                    out.close();
                }
                throw new Throwable();
            }
        }
    }

    /**
     * 上传文件到FTP服务器,支持断点续传
     * @param localFiles  本地文件名称,绝对路径 file("/home/directory1/subdirectory/file.ext")
     * @param remote 远程文件路径,使用/home/directory1/subdirectory/    不加文件
     * 按照Linux上的路径指定方式,支持多级目录嵌套,支持递归创建不存在的目录结构
     * @return 上传结果
     * @throws IOException
     */
    public void upload( String remote,File... localFiles) throws Throwable {
        if (StringUtils.isEmpty(remote) || localFiles == null || localFiles.length==0){
            log.error("本地文件为空");
            return;
        }
        remote = FTPPathToolkit.formatPathFTPIncludingSlash(remote);
        String result = "";

            mkdirs(remote); // 创建文件夹

            for (File file : localFiles) {
                if (file.isDirectory()) { // 上传目录
                    uploadFolder(getURL(remote), file);
                }
                else{
                    // 检查远程是否存在文件
                    FTPFile[] files = ftpClient.listFiles(new String((remote+file.getName()).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
                    System.out.println("文件是"+Arrays.asList(files).toString());
                    System.out.println("路径是"+new String((remote+file.getName()).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
                    if (files.length == 1) {
                        long remoteSize = files[0].getSize();
                        System.out.println(remoteSize+file.getPath());
                        long localSize = file.length();
                        if (remoteSize == localSize) {
                            System.out.println("不用上传已经有");
                            log.debug(file.getName()+" "+"上传成功");
                            continue;
                        } else if (remoteSize > localSize) {
                            log.debug(file.getName()+" "+"目标文件过大");
                            continue;
                        }
                        System.out.println(file.getName()+" "+"断点续传");
                        result = uploadFile(file.getName(), file, remoteSize);
                        log.debug(result+file.getPath());
                    } else {
                        System.out.println(file.getName()+" "+"新的上传");
                        result = uploadFile(file.getName(), file, 0);
                        log.debug(result+file.getPath());
                    }
                    if (result.equals("Upload_From_Break_Failed") || result.equals("Upload_New_File_Failed")) {
                        log.debug(result+file.getPath());
                    }
                }
            }
        }


    /**
     * 上传文件或目录
     *
     * @param dir
     *            目标文件
     *            是否删除源文件,默认为false
     *            文件或目录路径数组
     * @throws Exception
     */
    public void upload(String dir, String... paths)
            throws Throwable {
        if (dir==null || paths == null || paths.length ==0) {
            return;
        }
        File[] files = new File[paths.length];
        for (int i = 0; i < paths.length; i++) {
            files[i] = new File(paths[i]);
        }
        upload(dir,files);
    }

    public void uploadFolder(URL parentUrl,File file) throws Throwable {
        ftpClient.changeWorkingDirectory(new String(parentUrl.getPath().getBytes(LOCAL_CHARSET),StandardCharsets.ISO_8859_1));
        System.out.println("标记1"+parentUrl.getPath());
        String dir = file.getName(); // 当前目录名称
        URL url = getURL(parentUrl, dir);
        if (!exists( url.getPath())) { // 判断当前目录是否存在
            ftpClient.makeDirectory(new String(dir.getBytes(LOCAL_CHARSET),StandardCharsets.ISO_8859_1)); // 创建目录
            System.out.println("标记3");
        }
        ftpClient.changeWorkingDirectory(new String(dir.getBytes(LOCAL_CHARSET),StandardCharsets.ISO_8859_1));
        System.out.println("标记2"+dir);
        File[] files = file.listFiles(); // 获取当前文件夹所有文件及目录
        for (int i = 0; i < files.length; i++) {
            file = files[i];
            if (file.isDirectory()) { // 如果是目录,则递归上传
                uploadFolder(url, file);
            } else { // 如果是文件,直接上传
//                client.changeDirectory(url.getPath());
                // 检查远程是否存在文件
                String result;
                FTPFile[] ftpFiles = ftpClient.listFiles(new String((url.getPath()+"/"+file.getName()).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
                System.out.println("文件是"+Arrays.asList(ftpFiles).toString());
                System.out.println("路径是"+new String((url.getPath()+"/"+file.getName()).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
                if (ftpFiles.length == 1) {
                    System.out.println();
                    long remoteSize = ftpFiles[0].getSize();
                    long localSize = file.length();
                    if (remoteSize == localSize) {
                        System.out.println(file.getName()+" "+"不用上传已经有");
                        log.debug(file.getName()+" "+"上传成功");
                        continue;
                    } else if (remoteSize > localSize) {
                        log.debug(file.getName()+" "+"目标文件过大");
                        continue;
                    }
                    System.out.println(file.getName()+" "+"断点续传");
                    result = uploadFile(file.getName(), file, remoteSize);
                    log.debug(result+file.getPath());
                } else {
                    System.out.println(file.getName()+" "+"新的上传");
                    result = uploadFile(file.getName(), file, 0);
                    log.debug(result+file.getPath());
                }
                if (result == "Upload_From_Break_Failed" || result == "Upload_New_File_Failed") {
                    log.debug(result+file.getPath());

                }




            }
        }
    }

    /**
     * 判断文件或目录是否存在
     *
     *            FTP客户端对象
     * @param dir
     *            文件或目录
     * @return
     * @throws Exception
     */
    private boolean exists(String dir) throws Exception {
        return getFileType(dir) != -1;

    }

    /**
     * 判断当前为文件还是目录
     *
     *            FTP客户端对象
     * @param dir
     *            文件或目录
     * @return -1、文件或目录不存在 0、文件 1、目录
     */
    private int getFileType( String dir) {
        FTPFile[] files = null;
        try {
            files = ftpClient.listFiles(new String((dir).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));  //不存在目录也不报错
        } catch (Exception e) {
            return -1;
        }
        if (files.length > 1) {
            return FTPFile.DIRECTORY_TYPE;
        } else if (files.length == 1) {
            FTPFile f = files[0];
            if (f.getType() == FTPFile.DIRECTORY_TYPE) {
                return FTPFile.DIRECTORY_TYPE;
            }
            String path = FTPPathToolkit.formatPathFTP(dir) + "/" + f.getName();
            try {
                int len = ftpClient.listFiles(new String((path).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1)).length;
                if (len == 1) {
                    return FTPFile.DIRECTORY_TYPE;
                } else {
                    return FTPFile.FILE_TYPE;
                }
            } catch (Exception e) {
                return FTPFile.FILE_TYPE;
            }
        } else {
            try {
                if (!ftpClient.changeWorkingDirectory(new String((dir).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1))){
                    return -1;
                }
                ftpClient.changeToParentDirectory();
                return FTPFile.DIRECTORY_TYPE;
            } catch (Exception e) {
                return -1;
            }
        }
    }

    /**
     * 断开与远程服务器的连接
     * @throws IOException
     *
     */
    public void disconnect() throws IOException {
        if (ftpClient.isConnected()) {
            ftpClient.disconnect();
            System.out.println("关闭连接");
        }
    }
    /**
     *
     * 递归创建远程服务器目录
     * @param remote    远程服务器文件绝对路径
     * @return 目录创建是否成功
     * @throws IOException
     *
     */
    public String CreateDirecroty(String remote) throws IOException {
        String directory = remote.substring(0, remote.lastIndexOf("/") + 1);
        if (!directory.equalsIgnoreCase("/") && !ftpClient.changeWorkingDirectory(new String(directory.getBytes(LOCAL_CHARSET), "iso-8859-1"))) {
            // 如果远程目录不存在,则递归创建远程服务器目录
            int start = 0;
            int end = 0;
            if (directory.startsWith("/")) {
                start = 1;
            } else {
                start = 0;
            }
            end = directory.indexOf("/", start);
            while (true) {
                String subDirectory = new String(remote.substring(start, end).getBytes(LOCAL_CHARSET), "iso-8859-1");
                if (!ftpClient.changeWorkingDirectory(subDirectory)) {
                    if (ftpClient.makeDirectory(subDirectory)) {
                        ftpClient.changeWorkingDirectory(subDirectory);
                    } else {
                        System.out.println("创建目录失败");
                        return "Create_Directory_Fail";
                    }
                }
                start = end + 1;
                end = directory.indexOf("/", start);
                // 检查所有目录是否创建完毕
                if (end <= start) {
                    break;
                }
            }
        }
        return "Create_Directory_Success";
    }

    /**
     * 创建目录
     *            FTP客户端对象
     * @param dir
     *            目录绝对路径
     * @throws Exception
     */
    private void mkdirs(String dir) throws Exception {
        if (dir == null || dir.equals("")) {
            log.error("目标位置为空");
            return;
        }
        dir = FTPPathToolkit.formatPathFTP(dir);
//        dir = dir.replace("//", "/");
        String[] dirs = dir.split("/"); //如果/开头,会有一个空字符为数组索引0
        String path = "";
        for (int i = 0; i < dirs.length; i++) {
            path  +=  dirs[i] + "/";      //第一个dirs[0] 会是空  则 第一个path 为 /
            if (!isDirExist(path)) {
                if (!path.equals("/")){   //如果不是根目录
                if (!ftpClient.makeDirectory(new String(dirs[i].getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1))){
                    System.out.println("目录创建失败");
                    throw new Exception();
                }}
                System.out.println("创建目录"+path);
                ftpClient.changeWorkingDirectory(new String(path.getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));// 进入创建的目录
            }
        }
    }

    //检查目录是否存在
    private boolean isDirExist(String dir) {
        try {
            if (!ftpClient.changeWorkingDirectory(new String(dir.getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1))){
                return false;
            }
            System.out.println("切换目录"+dir);
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     *
     * 上传文件到服务器,新上传和断点续传
     * @param remoteFile  远程文件名,在上传之前已经将服务器工作目录做了改变
     * @param localFile   本地文件File句柄,绝对路径
     * @return
     * @throws IOException
     *
     */
    public String uploadFile(String remoteFile, File localFile,  long remoteSize)
            throws Throwable {
        RandomAccessFile raf = null;
        OutputStream out = null;
        try {
        String status = "";
        // 显示进度的上传
        long step = localFile.length() / 100;
        long process = 0;
        long localreadbytes = 0L;
           raf = new RandomAccessFile(localFile, "r");
           out = ftpClient.appendFileStream(new String(remoteFile.getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));/*追加续传*/
//         out = ftpClient.storeFileStream(new String(remoteFile.getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));/*重置为0开始传,覆盖,覆写*/
        // 断点续传
        if (remoteSize > 0) {
            System.out.println(localFile.getName()+" "+"续传!"+remoteSize);
            ftpClient.setRestartOffset(remoteSize);
            process = remoteSize / step;
            raf.seek(remoteSize);
            localreadbytes = remoteSize;
        }
        byte[] bytes = new byte[1024];
        int c;
        while ((c = raf.read(bytes)) != -1) {
            out.write(bytes, 0, c);
            localreadbytes += c;
//            if (localreadbytes / step != process) {
//                process = localreadbytes / step;
//                System.out.println("上传进度:" + process);
//                // TODO 汇报上传状态
//            }
        }
        out.flush();
        raf.close();
        out.close();
        boolean result = ftpClient.completePendingCommand();
        if (remoteSize > 0) {
            status = result ? "Upload_From_Break_Success" : "Upload_From_Break_Failed";
        } else {
            status = result ? "Upload_New_File_Success" : "Upload_New_File_Failed";
        }

        return status;
        }catch (Throwable throwable) {  //如果上传过程中断网,连接还在无法删除该文件,而且容易占资源不放。
            if (out!=null) {
                out.flush();
                out.close();
            }
            if (raf!=null) {
                raf.close();
            }
            ftpClient.disconnect();
            System.out.println("出异常关闭流"); //如果成功记得把下载也弄弄
            throwable.printStackTrace();
//            return "出异常关闭流 Failed";
            throw new Throwable();
        }
    }

    /**
     * 获取FTP目录
     *
     * @param url
     *            原FTP目录
     * @param dir
     *            目录
     * @return
     * @throws Exception
     */
    private URL getURL(URL url, String dir) throws Exception {
        String path = url.getPath();
        if (!path.endsWith("/") && !path.endsWith("//")) {
            path += "/";
        }
        dir = dir.replace("//", "/");
        if (dir.startsWith("/")) {
            dir = dir.substring(1);
        }
        path += dir;
        return new URL(url, path);
    }
    /**
     * 获取FTP目录
     *
     * @param dir
     *            目录
     * @return
     * @throws Exception
     */
    private URL getURL(String dir) throws Exception {
        if (dir.startsWith("/")) {
            dir = dir.substring(1);
        }
        return getURL(new URL("http://8.8.8.8"), dir);
    }

    /**
     * 测试方法
     * @author: tompai
     * @createTime: 2019年12月24日 下午11:08:24
     * @history:
     * @param args void
     */
    public static void main(String[] args) {
        FTPUtils myFtp = new FTPUtils();
        try {
//            Ftp ftp = new Ftp();
            System.out.println("编码为"+FTPUtils.LOCAL_CHARSET);
            System.err.println(myFtp.connect("221.224.163.15", 21, "FIH", "TqyjaGLxfGzj%AVE"));
//            System.err.println(myFtp.connect("10.132.240.89", 21, "it", "123456"));
//            projectName=new String(projectName.getBytes("UTF-8"),"iso-8859-1");
            System.out.println("编码为"+FTPUtils.LOCAL_CHARSET);
//            myFtp.upload("D:/3/测试.txt","/smart/测试.txt");
//            System.out.println(myFtp.ftpClient.printWorkingDirectory());
//            System.out.println(myFtp.ftpClient.changeWorkingDirectory(FTPUtils.+"/log"));
//            myFtp.ftpClient.changeWorkingDirectory(new String("DebugLog".getBytes(FTPUtils.LOCAL_CHARSET),"iso-8859-1"));
//            System.out.println(Arrays.toString(myFtp.ftpClient.listFiles(+"/log/DebugLog/")));
//            System.out.println(myFtp.ftpClient.changeWorkingDirectory(FTPUtils.+"/smart"));
//            System.out.println(myFtp.ftpClient.makeDirectory());
//            System.out.println(Arrays.toString(myFtp.ftpClient.listFiles(+"/smart/202008/202008241228512c103adcaf484989b6126168be788915.pdf")));
//            System.out.println(Arrays.toString(myFtp.ftpClient.listFiles(+"/smart/202008/")));
//            System.out.println(Arrays.toString(myFtp.ftpClient.listFiles(+"/smaasdasrt/20asdasd2008/")));
//            myFtp.ftpClient.changeWorkingDirectory(new String("权利的游戏".getBytes("GBK"),"iso-8859-1"));
//             myFtp.upload("/sample/", "D:\\3\\LOGC.zip");
            System.out.println(myFtp.download( "D:/5-1/","/sample/测试"));
            myFtp.disconnect();
        } catch (IOException e) {
            System.out.println("连接FTP出错:" + e.getMessage());
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}


FTPPathToolkit.java

package com.xkcoding.orm.mybatis.plus.utils;

import java.io.File;

public class FTPPathToolkit {

    /**
     * <p>Title: </p>
     * <p>Description: </p>
     */
    public FTPPathToolkit() {
        super();
        // TODO Auto-generated constructor stub
    }
    /**
     *
     * @Title: formatPath4File
     * @Description:格式化文件路径,将其中不规范的分隔转换为标准的分隔符,并且去掉末尾的文件路径分隔符。
     * 本方法操作系统自适应
     * @param @param path
     * @param @return
     * @return String
     * @throws
     */
    public static String formatPathFile(String path) {
        String reg0 = "\\\\+";
        String reg = "\\\\+|/+";
        String temp = path.trim().replaceAll(reg0, "/");
        temp = temp.replaceAll(reg, "/");
        if (temp.length() > 1 && temp.endsWith("/")) {
            temp = temp.substring(0, temp.length() - 1);
        }
        temp = temp.replace('/', File.separatorChar);
        return temp;
    }
    /**
     *
     * @Title: formatPathFTP
     * @Description: 格式化文件路径,将其中不规范的分隔转换为标准的分隔符 ,
     * 并且去掉末尾的"/"符号
     * @param @param path
     * @param @return
     * @return String
     * @throws
     */
    public static String formatPathFTP(String path) {
        String reg0 = "\\\\+";
        String reg = "\\\\+|/+";
        String temp = path.trim().replaceAll(reg0, "/");
        temp = temp.replaceAll(reg, "/");
        if (temp.length() > 1 && temp.endsWith("/")) {
            temp = temp.substring(0, temp.length() - 1);
        }
        return temp;
    }

    /**
     *
     * @Title: formatPathFTP
     * @Description: 格式化文件路径,将其中不规范的分隔转换为标准的分隔符 ,
     * 添加末尾的"/"符号
     * @param @param path
     * @param @return
     * @return String
     * @throws
     */
    public static String formatPathFTPIncludingSlash(String path) {
        String reg0 = "\\\\+";
        String reg = "\\\\+|/+";
        String temp = path.trim().replaceAll(reg0, "/");
        temp = temp.replaceAll(reg, "/");
        if (temp.length() > 1 && temp.endsWith("/")) {
            temp = temp.substring(0, temp.length() - 1);
        }
        temp = temp+"/";
        return temp;
    }
    /**
     *
     * @Title: genParentPath4FTP
     * @Description: 获取FTP路径的父路径,但不对路径有效性做检查
     * @param @param path
     * @param @return
     * @return String
     * @throws
     */
    public static String genParentPathFTP(String path) {
        String parentPath = new File(path).getParent();
        if (parentPath == null) return null;
        else return formatPathFTP(parentPath);
    }

    public static void main(String[] args) {
        String path = "//git//handwritten-tools";
        System.out.println(FTPPathToolkit.formatPathFTPIncludingSlash(path));
        String path1 = "//git//handwritten-tools//";
        System.out.println(FTPPathToolkit.formatPathFTPIncludingSlash(path1));
        String path2 = "//git//handwritten-tools//";
        System.out.println(FTPPathToolkit.formatPathFTPIncludingSlash(path2));
    }
}

使用方法

上传和下载各通过一个方法即可

    @Test
    public void test1() throws Throwable {
        FTPUtils ftpUtils = new FTPUtils();
        ftpUtils.connect("10.8.8.8",21,"F2848001","abcd1234");
        //上传使用方法演示
        String fromFtpDesc = "/Ftp/Menu/Data/";
        String toUploadFolder1 = "D:\\Git\\demo_token";
        String toUploadFile = "D:\\Git\\demo_token\\demo_token.txt";
        String[] uploadStrings = new String[2];
        uploadStrings[0] = toUploadFolder1;
        uploadStrings[1] = toUploadFile;
        ftpUtils.upload(fromFtpDesc,toUploadFolder1);   //上传文件夹字符串
        ftpUtils.upload(fromFtpDesc,toUploadFile);   //上传文件字符串
        ftpUtils.upload(fromFtpDesc,uploadStrings);   //上传文件类型数组。
        //下载使用方法演示
        String toWindosFile = "D:\\Git\\demo_token";
        String FromFtpDownloadFile1 = "/Ftp/Menu/Data/";
        String FromFtpDownloadFile2 = "/Ftp/Menu/a/66.txt";
        String[] downStrings = new String[2];
        downStrings[0] = "/Ftp/Menu/a/66.txt";
        downStrings[1] = "/Ftp/Menu/a/66.txt";
        ftpUtils.download(toWindosFile,FromFtpDownloadFile1);   //下载文件夹字符串
        ftpUtils.download(toWindosFile,FromFtpDownloadFile2);   //下载文件字符串
        ftpUtils.download(toWindosFile,downStrings);   //下载文件字符串
        ftpUtils.disconnect();
    }

该通用FTP功能齐全,而且兼容性挺好的,多仔细观察,能够通过一个upload或者download方法上传下载文件或文件夹,不用循环判断文件还是文件夹。

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
要在 Windows 中搭建一个支持下载文件文件服务器,可以使用 Nginx 和一些插件来实现。以下是步骤: 1. 下载 Nginx for Windows,可以从官网下载:http://nginx.org/en/download.html 2. 解压缩下载的 Nginx 文件到某个目录下,例如 C:\nginx。 3. 在 C:\nginx\conf 目录下创建一个名为 nginx.conf 的文件,并在其中添加以下配置: ``` worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root C:/nginx/html; index index.html index.htm; autoindex on; } location /upload { client_max_body_size 100m; alias C:/nginx/upload/; autoindex on; dav_methods PUT DELETE MKCOL COPY MOVE; dav_access user:rw group:rw all:r; } } } ``` 这个配置文件中定义了一个 http 服务,监听 80 端口,其中 /upload 路径是用来上文件的,其它路径是用来下载文件的。注意修改 root 和 alias 的路径为你自己的路径。 4. 在 C:\nginx\html 目录下创建一个名为 index.html 的文件,用于测试下载文件是否成功。 5. 在 C:\nginx\upload 目录下创建一个名为 index.html 的文件,用于测试上文件是否成功。 6. 启动 Nginx,打开命令行窗口,切换到 C:\nginx 目录下,执行命令:nginx.exe。 7. 打开浏览器,访问 http://localhost/,应该可以看到 index.html 页面,用于测试下载文件是否成功。 8. 打开浏览器,访问 http://localhost/upload/,应该可以看到 index.html 页面,用于测试上文件是否成功。 现在你的文件服务器已经搭建好了,可以用来上下载文件了。上文件时,可以使用 PUT 方法,例如使用 curl 命令上文件: ``` curl -T file.txt http://localhost/upload/ ``` 下载文件时,可以使用 GET 方法,例如使用浏览器访问 http://localhost/file.txt。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶孤崖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值