Java连接FTP服务器,并使用ftp连接池进行文件操作

使用Java连接FTP服务器进行文件相关操作,并且使用FTP连接池降低资源消耗,提高响应速率。

1、导入Pom依赖

         <!-- https://mvnrepository.com/artifact/commons-net/commons-net -->
        <dependency>
            <groupId>commons-net</groupId>
            <artifactId>commons-net</artifactId>
            <version>3.9.0</version>
        </dependency>

2、创建FTP的配置

ftp:
    # 服务器地址
    host: xx.xxx.xx.xxx
    # 端口号
    port: 21
    # 用户名
    userName: xxx
    # 密码
    password: xxxxxxx
    # 工作目录
    workingDirectory: /ftpTest
    # 编码
    encoding: utf-8
    #被动模式
    passiveMode: true
    #连接超时时间
    clientTimeout: 30000
    # 线程数
    threaNum: 1
    # 0=ASCII_FILE_TYPE(ASCII格式),1=EBCDIC_FILE_TYPE,2=LOCAL_FILE_TYPE(二进制文件)
    transferFileType: 2
    # 是否重命名
    renameUploaded: true
    # 重新连接时间
    retryTimes: 1200
    # 缓存大小
    bufferSize: 8192

    # 最大数
    maxTotal: 50
    # 最小空闲
    minldle: 10
    # 最大空闲
    maxldle: 50
    # 最大等待时间
    maxWait: 30000
    # 池对象耗尽之后是否阻塞,maxWait < 0 时一直等待
    blockWhenExhausted: true
    # 取对象时验证
    testOnBorrow: true
    # 回收验证
    testOnReturn: true
    # 创建时验证
    testOnCreate: true
    # 空闲验证
    testWhileldle: false
    # 后进先出
    lifo: false

3、创建FTP配置类

import lombok.Getter;
import lombok.Setter;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.apache.commons.net.ftp.FTPClient;


/**
 * Ftp配置类
 */
@Configuration
@ConfigurationProperties(prefix = "ftp")
@Getter
@Setter
public class FtpConfig extends GenericObjectPoolConfig<FTPClient> {

    /**
     * FTP服务器地址
     */
    private String host;

    /**
     * FTP服务器端口
     */
    private Integer port;

    /**
     * FTP用户名
     */
    private String userName;

    /**
     * FTP密码
     */
    private String password;

    /**
     * FTP服务器根目录
     */
    private String workingDirectory;

    /**
     * 传输编码
     */
    String encoding;

    /**
     * 被动模式:在这种模式下,数据连接是由客户程序发起的
     */
    boolean passiveMode;

    /**
     * 连接超时时间
     */
    int clientTimeout;

    /**
     * 线程数
     */
    int threaNum;
    /**
     * 0=ASCII_FILE_TYPE(ASCII格式),1=EBCDIC_FILE_TYPE,2=LOCAL_FILE_TYPE(二进制文件)
     */
    int transferFileType;

    /**
     * 是否重命名
     */
    boolean renameUploaded;

    /**
     * 重新连接时间
     */
    int retryTimes;

    /**
     * 缓存大小
     */
    int bufferSize;

    /**
     * 最大数
     */
    int maxTotal;

    /**
     * 最小空闲
     */
    int minldle;

    /**
     * 最大空闲
     */
    int maxldle;

    /**
     * 最大等待时间
     */
    int maxWait;
    /**
     *  池对象耗尽之后是否阻塞,maxWait < 0 时一直等待
     */
    boolean blockWhenExhausted;
    /**
     * 取对象时验证
     */
    boolean testOnBorrow;
    /**
     * 回收验证
     */
    boolean testOnReturn;
    /**
     * 创建时验证
     */
    boolean testOnCreate;
    /**
     * 空闲验证
     */
    boolean testWhileldle;
    /**
     * 后进先出
     */
    boolean lifo;
}

4、创建工厂连接对象并注入配置

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * FtpClient 工厂连接对象
 */
@Component
@Slf4j
public class FTPClientFactory implements PooledObjectFactory<FTPClient> {
    /**
     * 注入 ftp 连接配置
     */
    @Autowired
    FtpConfig config;

    /**
     * 创建连接到池中
     *
     * @return
     * @throws Exception
     */
    @Override
    public PooledObject<FTPClient> makeObject() throws Exception {
        FTPClient ftpClient = new FTPClient();
        ftpClient.setConnectTimeout(config.getClientTimeout());
        ftpClient.connect(config.getHost(), config.getPort());
        int reply = ftpClient.getReplyCode();
        if (!FTPReply.isPositiveCompletion(reply)) {
            ftpClient.disconnect();
            return null;
        }
        boolean success;
        if (StringUtils.isBlank(config.getUserName())) {
            success = ftpClient.login("anonymous", "anonymous");
        } else {
            success = ftpClient.login(config.getUserName(), config.getPassword());
        }
        if (!success) {
            return null;
        }
        ftpClient.setFileType(config.getTransferFileType());
        ftpClient.setBufferSize(1024);
        ftpClient.setControlEncoding(config.getEncoding());
        if (config.isPassiveMode()) {
            ftpClient.enterLocalPassiveMode();
        }
        log.debug("创建ftp连接");
        return new DefaultPooledObject<>(ftpClient);
    }

    /**
     * 链接状态检查
     *
     * @param pool
     * @return
     */
    @Override
    public boolean validateObject(PooledObject<FTPClient> pool) {
        FTPClient ftpClient = pool.getObject();
        try {
            return ftpClient != null && ftpClient.sendNoOp();
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 销毁连接,当连接池空闲数量达到上限时,调用此方法销毁连接
     *
     * @param pool
     * @throws Exception
     */
    @Override
    public void destroyObject(PooledObject<FTPClient> pool) throws Exception {
        FTPClient ftpClient = pool.getObject();
        if (ftpClient != null) {
            try {
                ftpClient.disconnect();
                log.debug("销毁ftp连接");
            } catch (Exception e) {
                log.error("销毁ftpClient异常,error:", e.getMessage());
            }
        }
    }

    /**
     * 钝化连接,是连接变为可用状态
     *
     * @param p
     * @throws Exception
     */
    @Override
    public void passivateObject(PooledObject<FTPClient> p) throws Exception{
        FTPClient ftpClient = p.getObject();
        try {
            ftpClient.changeWorkingDirectory(config.getWorkingDirectory());
            ftpClient.logout();
            if (ftpClient.isConnected()) {
                ftpClient.disconnect();
            }
        } catch (Exception e) {
            throw new RuntimeException("Could not disconnect from server.", e);
        }
    }

    /**
     * 初始化连接
     *
     * @param pool
     * @throws Exception
     */
    @Override
    public void activateObject(PooledObject<FTPClient> pool) throws Exception {
        FTPClient ftpClient = pool.getObject();
        ftpClient.connect(config.getHost(),config.getPort());
        ftpClient.login(config.getUserName(), config.getPassword());
        ftpClient.setControlEncoding(config.getEncoding());
        ftpClient.changeWorkingDirectory(config.getWorkingDirectory());
        //设置上传文件类型为二进制,否则将无法打开文件
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
    }

    /**
     * 获取 FTP 连接配置
     * @return
     */
    public FtpConfig getConfig(){
        return config;
    }
}

5、创建客户端对象service接口

import com.aicut.monitor.config.FtpConfig;
import org.apache.commons.net.ftp.FTPClient;

/**
 * 获取 ftp 客户端对象的接口
 */
public interface FTPPoolService {
    /**
     * 获取ftpClient
     * @return
     */
    FTPClient borrowObject();

    /**
     * 归还ftpClient
     * @param ftpClient
     * @return
     */
    void returnObject(FTPClient ftpClient);

    /**
     * 获取 ftp 配置信息
     * @return
     */
    FtpConfig getFtpPoolConfig();
}

6、创建FTP接口实现类 

import com.aicut.monitor.config.FTPClientFactory;
import com.aicut.monitor.config.FtpConfig;
import com.aicut.monitor.service.FTPPoolService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;


@Component
@Slf4j
public class FTPPoolServiceImpl implements FTPPoolService {

    /**
     * ftp 连接池生成
     */
    private GenericObjectPool<FTPClient> pool;

    /**
     * ftp 客户端配置文件
     */
    @Autowired
    private FtpConfig config;

    /**
     * ftp 客户端工厂
     */
    @Autowired
    private FTPClientFactory factory;

    /**
     * 初始化pool
     */
    @PostConstruct
    private void initPool() {
        this.pool = new GenericObjectPool<FTPClient>(this.factory, this.config);
    }

    /**
     * 获取ftpClient
     */
    @Override
    public FTPClient borrowObject() {
        if (this.pool != null) {
            try {
                return this.pool.borrowObject();
            } catch (Exception e) {
                log.error("获取 FTPClient 失败 ", e);
            }
        }
        return null;
    }

    /**
     * 归还 ftpClient
     */
    @Override
    public void returnObject(FTPClient ftpClient) {
        if (this.pool != null && ftpClient != null) {
            this.pool.returnObject(ftpClient);
        }
    }

    @Override
    public FtpConfig getFtpPoolConfig() {
        return config;
    }
}

7、FTP工具类

import cn.hutool.core.util.CharsetUtil;
import com.aicut.monitor.enums.DownloadStatus;
import com.aicut.monitor.enums.UploadStatus;
import com.aicut.monitor.enums.uploadImageType;
import com.aicut.monitor.service.FTPPoolService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator;
import org.apache.commons.compress.archivers.zip.UnixStat;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.parallel.InputStreamSupplier;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.NullInputStream;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * FTP工具类
 *
 * @author YJ2023085043
 */
@Component
@Slf4j
public class FtpUtil {

    /**
     * ftp 连接池
     */
    @Autowired
    FTPPoolService ftpPoolService;

    public static final String DIR_SPLIT = "/";

    public static final String HTTP_protocol = "http://";


    /**
     * 上传单个文件
     *
     * @param uploadPath 上传路径
     * @param fileName   文件名
     * @param input      文件输入流
     * @return 上传结果
     */
    public UploadStatus upload(String uploadPath, String fileName, InputStream input) {
        FTPClient ftpClient = ftpPoolService.borrowObject();
        try {
            // 切换到工作目录
            if (!ftpClient.changeWorkingDirectory(uploadPath)) {
                ftpClient.makeDirectory(uploadPath);
                ftpClient.changeWorkingDirectory(uploadPath);
            }
            // 文件写入
            boolean storeFile = ftpClient.storeFile(fileName, input);
            if (storeFile) {
                log.info("文件:{}上传成功", fileName);
                return UploadStatus.UploadNewFileSuccess;
            } else {
                throw new RuntimeException("ftp文件写入异常");
            }
        } catch (IOException e) {
            log.error("文件:{}上传失败", fileName, e);
            return UploadStatus.UploadNewFileFailed;
        } finally {
                IOUtils.closeQuietly(input);
                ftpPoolService.returnObject(ftpClient);
            }
    }

    /**
     * 从FTP服务器上下载文件,支持断点续传,下载百分比汇报
     *
     * @param ftpPath 远程文件路径
     * @param fileName 远程文件名
     * @param local  本地文件完整绝对路径
     * @return 下载的状态
     * @throws IOException
     */
    public DownloadStatus downloadFile(String ftpPath, String fileName, String local) throws IOException {
        FTPClient ftpClient = ftpPoolService.borrowObject();
        // 设置被动模式,由于Linux安全性考虑,端口没有全部放开,所有被动模式不能用
        ftpClient.enterLocalPassiveMode();
        // 设置以二进制方式传输
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
        DownloadStatus result;
        try {
            // 检查远程文件是否存在
            FTPFile[] files = ftpClient.listFiles(ftpPath,file -> file.getName().equals(fileName));
            if (files.length != 1) {
                log.info("远程文件不存在");
                return DownloadStatus.RemoteFileNotExist;
            }
            long lRemoteSize = files[0].getSize();
            File f = new File(local+DIR_SPLIT+fileName);
            // 本地存在文件,进行断点下载
            if (f.exists()) {
                long localSize = f.length();
                // 判断本地文件大小是否大于远程文件大小
                if (localSize >= lRemoteSize) {
                    log.info("本地文件大于远程文件,下载中止");
                    return DownloadStatus.LocalFileBiggerThanRemoteFile;
                }
                // 进行断点续传,并记录状态
                FileOutputStream out = new FileOutputStream(f, true);
                ftpClient.setRestartOffset(localSize);
                InputStream in = ftpClient.retrieveFileStream(ftpPath + DIR_SPLIT + fileName);
                byte[] bytes = new byte[1024];
                long step = lRemoteSize / 100;
                // 文件过小,step可能为0
                step = step == 0 ? 1 : step;
                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) {
                            log.info("下载进度:" + process);
                        }
                    }
                }
                in.close();
                out.close();
                boolean isDo = ftpClient.completePendingCommand();
                if (isDo) {
                    result = DownloadStatus.DownloadFromBreakSuccess;
                } else {
                    result = DownloadStatus.DownloadFromBreakFailed;
                }
            } else {
                OutputStream out = new FileOutputStream(f);
                InputStream in = ftpClient.retrieveFileStream(ftpPath + DIR_SPLIT + fileName);
                byte[] bytes = new byte[1024];
                long step = lRemoteSize / 100;
                // 文件过小,step可能为0
                step = step == 0 ? 1 : step;
                long process = 0;
                long 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) {
                            log.info("下载进度:" + process);
                        }
                    }
                }
                in.close();
                out.close();
                boolean upNewStatus = ftpClient.completePendingCommand();
                if (upNewStatus) {
                    result = DownloadStatus.DownloadNewSuccess;
                } else {
                    result = DownloadStatus.DownloadNewFailed;
                }
            }
        } catch (Exception e) {
            log.error("download error", e);
        } finally {
            ftpPoolService.returnObject(ftpClient);
        }
        return DownloadStatus.DownloadNewFailed;
    }

    /**
     * 下载文件到本地 *
     *
     * @param ftpPath     FTP服务器文件目录 *
     * @param ftpFileName 文件名称 *
     * @param localPath   下载后的文件路径 *
     * @return
     */
    public boolean download(String ftpPath, String ftpFileName, String localPath) {
        FTPClient ftpClient = ftpPoolService.borrowObject();
        OutputStream outputStream = null;
        try {
            FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.isFile() && file.getName().equals(ftpFileName));
            if (ftpFiles != null && ftpFiles.length > 0) {
                FTPFile ftpFile = ftpFiles[0];
                File localFile = new File(localPath + DIR_SPLIT + ftpFile.getName());
                // 判断本地路径目录是否存在,不存在则创建
                if (!localFile.getParentFile().exists()) {
                    localFile.getParentFile().mkdirs();
                }
                outputStream = Files.newOutputStream(localFile.toPath());
                ftpClient.retrieveFile(ftpFile.getName(), outputStream);

                log.info("fileName:{},size:{}", ftpFile.getName(), ftpFile.getSize());
                log.info("下载文件成功...");
                return true;
            } else {
                log.info("文件不存在,filePathname:{},", ftpPath + DIR_SPLIT + ftpFileName);
            }
        } catch (Exception e) {
            log.error("下载文件失败...",e);
        } finally {
            IOUtils.closeQuietly(outputStream);
            ftpPoolService.returnObject(ftpClient);
        }
        return false;
    }

    /**
     * 下载文件到浏览器 *
     *
     * @param ftpPath     FTP服务器文件目录 *
     * @param ftpFileName 文件名称 *
     * @param response
     * @return
     */
    public void download(HttpServletResponse response, String ftpPath, String ftpFileName)  {
        FTPClient ftpClient = ftpPoolService.borrowObject();
        OutputStream outputStream = null;
        try {
            FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.isFile() && file.getName().equals(ftpFileName));
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("utf8");
            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(ftpFileName,"UTF-8") );
            outputStream = response.getOutputStream();
            if (ftpFiles != null && ftpFiles.length > 0) {
                FTPFile ftpFile = ftpFiles[0];
                ftpClient.retrieveFile(ftpPath+DIR_SPLIT+ftpFile.getName(), outputStream);

                log.info("fileName:{},size:{}", ftpFile.getName(), ftpFile.getSize());
                log.info("下载文件成功...");
            } else {
                log.info("文件不存在,filePathname:{},", ftpPath + DIR_SPLIT + ftpFileName);
            }
        } catch (Exception e) {
            log.error("下载文件失败...",e);
        } finally {
            IOUtils.closeQuietly(outputStream);
            ftpPoolService.returnObject(ftpClient);
        }
    }

    public void ftpZipFileDownload (HttpServletResponse response,String ftpPath) {
        //从FTP上下载文件并打成ZIP包给用户下载
        ZipOutputStream zipOut = null;
        try {
            //文件名称
            String zipFileName = "导出数据.zip";
            response.reset();
            // 设置导出文件头
            response.setContentType("application/octet-stream");
            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(zipFileName,"UTF-8") );
            // 定义Zip输出流
            zipOut = new ZipOutputStream(response.getOutputStream());
            zipFTPFile(ftpPath,zipOut,"");
        } catch (IOException e) {
            log.error("当前:"+ftpPath+"下载FTP文件--->下载文件失败:"+e.getMessage());
        } finally {
            // 关闭zip文件输出流
            if (null != zipOut) {
                try {
                    zipOut.closeEntry();
                    zipOut.close();
                } catch (IOException e) {
                    log.error("当前:"+ftpPath+"下载FTP文件--->关闭zip文件输出流出错:"+e.getMessage());
                }
            }
        }
    }

    public void zipFTPFile(String ftpPath, ZipOutputStream zipOut,String foldPath){
        FTPClient ftpClient = ftpPoolService.borrowObject();
        try {
            // 切换到指定目录中,如果切换失败说明目录不存在
            if(!ftpClient.changeWorkingDirectory(ftpPath)){
                log.error("切换目录失败");
                throw new RuntimeException("切换目录失败");
            }
            // 每次数据连接之前,ftp client告诉ftp server开通一个端口来传输数据
            ftpClient.enterLocalPassiveMode();
            // 遍历路径下的所有文件
            FTPFile[] fileList = ftpClient.listFiles();
            byte[] byteReader = new byte[1024];
            ByteArrayOutputStream os = null;
            for (FTPFile tempFile : fileList) {
                if (tempFile.isFile()) {
                    os = new ByteArrayOutputStream();
                    // 从FTP上下载downFileName该文件把该文件转化为字节数组的输出流
                    ftpClient.retrieveFile(tempFile.getName(), os);
                    byte[] bytes = os.toByteArray();
                    InputStream ins = new ByteArrayInputStream(bytes);

                    int len;
                    zipOut.putNextEntry(new ZipEntry(foldPath + tempFile.getName()));
                    // 读入需要下载的文件的内容,打包到zip文件
                    while ((len = ins.read(byteReader)) > 0) {
                        zipOut.write(byteReader, 0, len);
                    }
                }else{
                    //如果是文件夹,则递归调用该方法
                    zipOut.putNextEntry(new ZipEntry(tempFile.getName() + DIR_SPLIT));
                    zipFTPFile(ftpPath + DIR_SPLIT + tempFile.getName(), zipOut, tempFile.getName() + DIR_SPLIT);
                }
            }
            zipOut.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            ftpPoolService.returnObject(ftpClient);
        }
    }

    /**
     * 得到某个目录下的文件名列表
     *
     * @param ftpDirPath FTP上的目标文件路径
     * @return
     * @throws IOException
     */
    public List<String> getFileList(String ftpDirPath) {
        FTPClient ftpClient = ftpPoolService.borrowObject();
        try {
            FTPFile[] ftpFiles = ftpClient.listFiles(ftpDirPath);
            if (ftpFiles != null && ftpFiles.length > 0) {
                return Arrays.stream(ftpFiles).map(FTPFile::getName).collect(Collectors.toList());
            }
            log.error(String.format("路径有误,或目录【%s】为空", ftpDirPath));
        } catch (Exception e) {
            log.error("获取目录下文件列表失败", e);
        } finally {
            ftpPoolService.returnObject(ftpClient);
        }
        return null;
    }

    /**
     * 删除文件
     *
     * @param ftpPath 服务器文件存储路径
     * @param fileName 文件名
     * @return
     * @throws IOException
     */
    public boolean deleteFile(String ftpPath, String fileName) {
        FTPClient ftpClient = ftpPoolService.borrowObject();
        try {
            // 在 ftp 目录下获取文件名与 fileName 匹配的文件信息
            FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.getName().equals(fileName));
            // 删除文件
            if (ftpFiles != null && ftpFiles.length > 0) {
                boolean del;
                String deleteFilePath = ftpPath + DIR_SPLIT + fileName;
                FTPFile ftpFile = ftpFiles[0];
                if (ftpFile.isDirectory()) {
                    //递归删除该目录下的所有文件后删除目录
                    FTPFile[] files = ftpClient.listFiles(ftpPath + DIR_SPLIT + fileName);
                    for (FTPFile file : files) {
                        if(file.isDirectory()){
                            deleteFile(ftpPath + DIR_SPLIT + fileName,file.getName());
                        }else{
                            del = ftpClient.deleteFile(deleteFilePath + DIR_SPLIT + file.getName());
                            log.info(del ? "文件:{}删除成功" : "文件:{}删除失败", file.getName());
                        }
                    }
                    del = ftpClient.removeDirectory(deleteFilePath);
                } else {
                    del = ftpClient.deleteFile(deleteFilePath);
                }
                log.info(del ? "文件:{}删除成功" : "文件:{}删除失败", fileName);
                return del;
            } else {
                log.warn("文件:{}未找到", fileName);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            ftpPoolService.returnObject(ftpClient);
        }
        return false;
    }

    /**
     * 上传文件到FTP服务器,支持断点续传
     * @param uploadPath 远程文件存放路径
     * @param fileName 上传文件名
     * @param input 文件输入流
     * @return 上传结果
     * @throws IOException
     */
    public UploadStatus uploadFile(String uploadPath, String fileName, InputStream input) {
        FTPClient ftpClient = ftpPoolService.borrowObject();
        UploadStatus result = UploadStatus.UploadNewFileFailed;
        try {
            // 设置PassiveMode传输
            ftpClient.enterLocalPassiveMode();
            // 设置以二进制流的方式传输
            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            ftpClient.setControlEncoding(CharsetUtil.UTF_8);
            //切换到工作目录
            if(!ftpClient.changeWorkingDirectory(uploadPath)){
                ftpClient.makeDirectory(uploadPath);
                ftpClient.changeWorkingDirectory(uploadPath);
            }
            // 检查远程是否存在文件
            FTPFile[] files = ftpClient.listFiles(uploadPath,file -> file.getName().equals(fileName));
            if (files.length == 1) {
                long remoteSize = files[0].getSize();
                //根据文件输入流获取文件对象
                File f = getFileFromInputStream(input);
                long localSize = f.length();
                // 文件存在
                if (remoteSize == localSize) {
                    return UploadStatus.FileExits;
                } else if (remoteSize > localSize) {
                    return UploadStatus.RemoteFileBiggerThanLocalFile;
                }
                // 尝试移动文件内读取指针,实现断点续传
                result = uploadFile(fileName, f, ftpClient, remoteSize);
                // 如果断点续传没有成功,则删除服务器上文件,重新上传
                if (result == UploadStatus.UploadFromBreakFailed) {
                    if (!ftpClient.deleteFile(fileName)) {
                        return UploadStatus.DeleteRemoteFaild;
                    }
                    result = uploadFile(fileName, f, ftpClient, 0);
                }
            } else {
                result = uploadFile(fileName, getFileFromInputStream(input), ftpClient, 0);
            }
        } catch (Exception e) {
            log.error("上传文件失败", e);
        } finally {
            ftpPoolService.returnObject(ftpClient);
        }
        return result;
    }

    /**
     * 从输入流中获取文件对象
     * @param inputStream
     * @return
     */
    public static File getFileFromInputStream(InputStream inputStream) {
        File file = null;
        try {
            // 创建临时文件
            file = File.createTempFile("temp", null);

            // 将输入流写入临时文件
            byte[] buffer = new byte[1024];
            int bytesRead;
            try (FileOutputStream outputStream = new FileOutputStream(file)) {
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }

    /**
     * 递归创建远程服务器目录
     *
     * @param remote    远程服务器文件绝对路径
     * @param ftpClient FTPClient对象
     * @return 目录创建是否成功
     * @throws IOException
     */

    public UploadStatus createDirectory(String remote, FTPClient ftpClient) throws IOException {
        UploadStatus status = UploadStatus.CreateDirectorySuccess;
        String directory = remote.substring(0, remote.lastIndexOf("/") + 1);
        if (!directory.equalsIgnoreCase("/") && !ftpClient.changeWorkingDirectory(new String(directory.getBytes(CharsetUtil.UTF_8), CharsetUtil.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(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1);
                if (!ftpClient.changeWorkingDirectory(subDirectory)) {
                    if (ftpClient.makeDirectory(subDirectory)) {
                        ftpClient.changeWorkingDirectory(subDirectory);
                    } else {
                        log.info("创建目录失败");
                        return UploadStatus.CreateDirectoryFail;
                    }
                }
                start = end + 1;
                end = directory.indexOf("/", start);
                // 检查所有目录是否创建完毕
                if (end <= start) {
                    break;
                }
            }
        }
        return status;
    }

    /**
     * 上传文件到服务器,新上传和断点续传
     *
     * @param remoteFileName 远程文件名,在上传之前已经将服务器工作目录做了改变,一定要注意这里的 remoteFile 已经别被编码 ISO-8859-1
     * @param localFile  本地文件File句柄,绝对路径
     * @param ftpClient  FTPClient引用
     * @return
     * @throws IOException
     */
    public UploadStatus uploadFile(String remoteFileName, File localFile, FTPClient ftpClient, long remoteSize) {
        if (null == ftpClient) {
            ftpClient = ftpPoolService.borrowObject();
        }
        if (null == ftpClient) {
            return null;
        }
        UploadStatus status = UploadStatus.UploadNewFileFailed;

        try (RandomAccessFile raf = new RandomAccessFile(localFile, "r");
             OutputStream out = ftpClient.appendFileStream(remoteFileName);) {
            // 显示进度的上传
            log.info("localFile.length():" + localFile.length());
            long step = localFile.length() / 100;
            // 文件过小,step可能为0
            step = step == 0 ? 1 : step;
            long process = 0;
            long localreadbytes = 0L;

            // 断点续传
            if (remoteSize > 0) {
                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;
                    if (process % 10 == 0) {
                        log.info("上传进度:" + process);
                    }
                }
            }
            out.flush();
            raf.close();
            out.close();
            // FTPUtil的upload方法在执行ftpClient.completePendingCommand()之前应该先关闭OutputStream,否则主线程会在这里卡死执行不下去。
            // 原因是completePendingCommand()会一直在等FTP Server返回226 Transfer complete,但是FTP Server只有在接受到OutputStream执行close方法时,才会返回。
            boolean result = ftpClient.completePendingCommand();
            if (remoteSize > 0) {
                status = result ? UploadStatus.UploadFromBreakSuccess : UploadStatus.UploadFromBreakFailed;
            } else {
                status = result ? UploadStatus.UploadNewFileSuccess : UploadStatus.UploadNewFileFailed;
            }
        } catch (Exception e) {
            log.error("uploadFile error ", e);
        }
        return status;
    }

    /**
     * 获取FTP某一特定目录下的文件数量
     *
     * @param ftpDirPath FTP上的目标文件路径
     */
    public Integer getFileNum(String ftpDirPath) {
        FTPClient ftpClient = ftpPoolService.borrowObject();
        try {
            FTPFile[] ftpFiles = ftpClient.listFiles(ftpDirPath);
            if (ftpFiles != null && ftpFiles.length > 0) {
                return Arrays.stream(ftpFiles).map(FTPFile::getName).collect(Collectors.toList()).size();
            }
            log.error(String.format("路径有误,或目录【%s】为空", ftpDirPath));
        } catch (IOException e) {
            log.error("文件获取异常:", e);
        } finally {
            ftpPoolService.returnObject(ftpClient);
        }
        return null;
    }

    /**
     * 获取文件夹下文件数量
     * @param ftpPath
     * @return
     */
    public Map<String,String> getDirFileNum(String ftpPath) {
        FTPClient ftpClient = ftpPoolService.borrowObject();
        try {
            Integer sum  = 0;
            Map<String,String> map = new HashMap<>();
            FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath);
            if (ftpFiles != null && ftpFiles.length > 0) {
                for (FTPFile file : ftpFiles) {
                    if (file.isDirectory()) {
                        sum += getFileNum(ftpPath + DIR_SPLIT + file.getName());
                        map.put(file.getName(), String.valueOf(getFileNum(ftpPath + DIR_SPLIT + file.getName())));
                    }
                }
            }else {
                log.error(String.format("路径有误,或目录【%s】为空", ftpPath));
            }
            map.put("sum", String.valueOf(sum));
            return map;
        } catch (IOException e) {
            log.error("文件获取异常:", e);
        } finally {
            ftpPoolService.returnObject(ftpClient);
        }
        return null;
    }


    /**
     * 下载指定文件夹到本地
     * @param ftpPath FTP服务器文件目录
     * @param localPath 下载后的文件路径
     * @param dirName 文件夹名称
     * @return
     */
    public void downloadDir(String ftpPath, String localPath, String dirName){
        FTPClient ftpClient = ftpPoolService.borrowObject();
        OutputStream outputStream = null;
        try {
            //判断是否存在该文件夹
            FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath, file -> file.getName().equals(dirName));
            if (ftpFiles != null && ftpFiles.length > 0) {
                if(ftpFiles[0].isDirectory()) {
                    // 判断本地路径目录是否存在,不存在则创建
                    File localFile = new File(localPath + DIR_SPLIT + dirName);
                    if (!localFile.exists()) {
                        localFile.mkdirs();
                    }
                    for (FTPFile file : ftpClient.listFiles(ftpPath + DIR_SPLIT + dirName)) {
                        if (file.isDirectory()) {
                            downloadDir(ftpPath + DIR_SPLIT + dirName, localPath + dirName + DIR_SPLIT, file.getName());
                        } else {
                            outputStream = Files.newOutputStream(new File(localPath + DIR_SPLIT + dirName + DIR_SPLIT + file.getName()).toPath());
                            ftpClient.retrieveFile(ftpPath + DIR_SPLIT + dirName + DIR_SPLIT + file.getName(), outputStream);
                            log.info("fileName:{},size:{}", file.getName(), file.getSize());
                            outputStream.close();
                        }
                    }
                }
            }
        }catch (Exception e){
            log.error("下载文件夹失败,filePathname:{},", ftpPath + DIR_SPLIT + dirName, e);
        }finally {
            IOUtils.closeQuietly(outputStream);
            ftpPoolService.returnObject(ftpClient);
        }
    }

        /**
     * 从本地上传文件夹
     * @param uploadPath ftp服务器地址
     * @param localPath 本地文件夹地址
     * @param dirName  文件夹名称
     * @return
     */
    public boolean uploadDir(String uploadPath, String localPath, String dirName){
        FTPClient ftpClient = ftpPoolService.borrowObject();
        try{
            // 切换到工作目录
            if (!ftpClient.changeWorkingDirectory(uploadPath)) {
                ftpClient.makeDirectory(uploadPath);
                ftpClient.changeWorkingDirectory(uploadPath);
            }
            //创建文件夹
            ftpClient.makeDirectory(uploadPath + DIR_SPLIT + dirName);
            File src = new File(localPath);
            //获取该目录下的所有文件
            File[] files = src.listFiles();
            FileInputStream input = null;
            for(File file : files) {
                if (file.isDirectory()) {
                    uploadDir(uploadPath + DIR_SPLIT + dirName, file.getAbsolutePath(), file.getName());
                } else {
                    input = new FileInputStream(file);
                    boolean storeFile = ftpClient.storeFile(uploadPath + DIR_SPLIT + dirName + DIR_SPLIT + file.getName(), input);
                    if (storeFile) {
                        log.info("文件:{}上传成功", file.getName());
                    } else {
                        throw new RuntimeException("ftp文件写入异常");
                    }
                }
            }
            if(input != null){
                input.close();
            }
        }catch (Exception e){
            log.error("文件夹上传失败",e);
        }finally {
            ftpPoolService.returnObject(ftpClient);
        }
        return false;
    }
}

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

顾十方

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

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

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

打赏作者

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

抵扣说明:

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

余额充值