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;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/572991.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mPEG-Dansyl,Methoxy PEG Dansyl由甲氧基-聚乙二醇(mPEG)和丹磺酰氯(Dansyl)两部分组成

【试剂详情】 英文名称 mPEG-Dansyl&#xff0c;Methoxy PEG Dansyl 中文名称 聚乙二醇单甲醚丹磺酸酯&#xff0c;甲氧基-聚乙二醇-丹磺酰胺 外观性状 由分子量决定&#xff0c;液体或者固体 分子量 0.4k&#xff0c;0.6k&#xff0c;1k&#xff0c;2k&#xff0c;3.4k…

Fisher 准则分类

目录 一、什么是Fisher 准则 二、具体实例 三、代码实现 四、结果 一、什么是Fisher 准则 Fisher准则&#xff0c;即Fisher判别准则&#xff08;Fisher Discriminant Criterion&#xff09;&#xff0c;是统计学和机器学习中常用的一种分类方法&#xff0c;由统计学家罗纳…

JuliaImages教程(二):图像分割

1、介绍 图像分割是将图像划分为具有相似属性的区域的过程。图像分割具有多种应用&#xff0c;例如医学图像分割、图像压缩&#xff0c;并用作对象检测和光流等更高级别视觉任务中的预处理步骤。该包是用 Julia 编写的图像分割算法的集合。 2、安装 Pkg.add("ImageSegm…

软件测试面试题(二)

Web 测试.web 测试描述用浏览器访问 www.baidu.com 的过程以京东首页为例&#xff0c;设计用例框架。&#xff08;注意框架设计逻辑&#xff0c;区域划分&#xff0c;专项测试等&#xff0c;不需 要详细用例&#xff0c;需要查看 PC 可直接和辨识管提要求&#xff09;如何测试购…

Java Web 网页设计(1)

不要让追求之舟停泊在幻想的港湾 而应扬起奋斗的风帆 驶向现实生活的大海 网页设计 1.首先 添加框架支持 找到目录右键添加 找到Web Application选中 点击OK 然后 编辑设置 找到Tomcat--local 选中 点击OK 名称可以自己设置 找到对应文件夹路径 把Tomcat添加到项目里面 因为…

C++之通俗易懂学模版

目录 一、了解什么是泛性编程 二、模版 1.函数模版 1.1 函数模板概念 1.2 函数模板格式 1.3 函数模板的原理 1.4 函数模板的实例化 1.5 模板参数的匹配原则 2.类模板 2.1 类模板的定义格式 2.2 类模板的实例化 3. 非类型模板参数 4. 模板的特化 4.1 概念 4.2 …

Visual Studio调试C/C++指南

1. 前言 Visual Studio&#xff08;VS&#xff09;是微软开发的一款集成开发环境(IDE)软件&#xff0c;支持C/C、C#、VB、Python等开发语言&#xff0c;开发桌面、Web等应用程序。VS功能极其强大&#xff0c;使用极其便利&#xff0c;用户数量最多&#xff0c;被誉为"宇宙…

Python 基础 (Pandas):Pandas 入门

1. 官方文档 API reference — pandas 2.2.2 documentation 2. 准备知识&#xff1a;Pandas 数据结构 Series & DataFrame 2.1 Series 2.1.1 创建 Series 类型数据 一个 Series 对象包含两部分&#xff1a;值序列、标识符序列。可通过 .values (返回 NumPy ndarry 类型…

C语言扫雷游戏完整实现(下)

文章目录 前言一、排雷函数菜单二、排雷函数菜单的实现三、拓展棋盘功能四、源码1. test.c源文件2. game.h头文件3. game.c源文件 总结 前言 C语言实现扫雷游戏的排雷菜单&#xff0c;以及功能的实现&#xff0c;拓展棋盘功能&#xff0c;以及源码等。 上半部分的链接地址: C语…

第十五届蓝桥杯省赛第二场PythonB组B题【逆序对期望】题解(AC)

解题思路 枚举所有的可能的交换情况&#xff0c;时间复杂度 O ( n 4 ) O(n^4) O(n4)。 用归并排序计算数组的逆序对&#xff0c;时间复杂度 O ( n ) O(n) O(n)。 综上时间复杂度 O ( n 5 ) O(n^5) O(n5)。 由于 Python 运行效率较低&#xff0c;约 500 500 500 秒可得到…

前端框架技术调研

目前程序员使用前端框架最多的是哪一个&#xff1f;

SEGGER Embedded Studio IDE移植FreeRTOS

SEGGER Embedded Studio IDE移植FreeRTOS 一、简介二、技术路线2.1 获取FreeRTOS源码2.2 将必要的文件复制到工程中2.2.1 移植C文件2.2.2 移植portable文件2.2.3 移植头文件 2.3 创建FreeRTOSConfig.h并进行配置2.3.1 处理中断优先级2.3.2 configASSERT( x )的处理2.3.3 关于系…

echarts树图-实现拓扑图效果

使用echarts树图来实现拓扑图效果&#xff0c;其效果如下&#xff1a; 代码如下&#xff1a; const data {name: XXX公司,children: [{name: 网络主机,children: [{name: 普通路由器,children: [{name: 智能网关},{name: 192.168.1.0/24}]}]},{name: 企业路由器},{name: 三…

【分享】WinRAR软件如何压缩文件?

WinRAR是一款功能强大的压缩文件管理工具&#xff0c;支持多种压缩文件格式&#xff0c;那如何使用WinRAR来压缩文件呢&#xff1f;不清楚的小伙伴一起来看看吧&#xff01; 压缩方法&#xff1a; 首先&#xff0c;安装好WinRAR工具&#xff0c;然后选中需要压缩的文件或文件夹…

C++高级特性:异常概念与处理机制(十四)

1、异常的基本概念 异常&#xff1a;是指在程序运行的过程中发生的一些异常事件&#xff08;如&#xff1a;除数为0&#xff0c;数组下标越界&#xff0c;栈溢出&#xff0c;访问非法内存等&#xff09; C的异常机制相比C语言的异常处理&#xff1a; 函数的返回值可以忽略&…

麒麟龙芯loongarch64 electron 打包deb包

在麒麟龙芯&#xff08;loongarch64&#xff09;电脑上 使用electron 开发桌面应用。之前用electron-packager 打包出来的是文件夹 是 unpack 包。现在需要打包deb包&#xff0c;依据开发指南开始打包。 在项目文件夹下 打开终端 输入 npm run packager 先打包unpack包 然后…

FreeRTOS:3.消息队列

FreeRTOS消息队列 本文主要基于消息队列的源码进行分析&#xff0c;来对FreeRTOS的消息队列进一步学习。 消息队列非常重要&#xff0c;因为后面的各种信号量基本都是基于队列的&#xff0c;搞清楚消息队列的源码&#xff0c;也就搞清楚消息队列的原理。 参考链接&#xff1…

188页 | 2023企业数字化转型建设方案(数据中台、业务中台、AI中台)(免费下载)

1、知识星球下载&#xff1a; 如需下载完整PPTX可编辑源文件&#xff0c;请前往星球获取&#xff1a;https://t.zsxq.com/19KcxSeyA 2、免费领取步骤&#xff1a; 【1】关注公众号 方案驿站 【2】私信发送 【2023企业数字化转型建设方案】 【3】获取本方案PDF下载链接&#…

AI:165-Coze自定义赛博风格Bot-图片生成操作指南

Coze是由字节跳动推出的一个AI聊天机器人和应用程序编辑开发平台&#xff0c;旨在帮助用户快速创建各种类型的聊天机器人、智能体、AI应用和插件&#xff0c;并将其部署在社交平台和即时聊天应用程序中&#xff0c;如Discord、WhatsApp、Twitter、飞书、微信公众号等。 这个平…

计算机网络3——数据链路层3以太网的MAC层

文章目录 一、MAC 层的硬件地址1、介绍2、注意点3、定制标准 二、MAC 帧的格式1、结构2、工作原理3、其他 一、MAC 层的硬件地址 1、介绍 在局域网中&#xff0c;硬件地址又称为物理地址或 MAC地址(因为这种地址用在MAC帧中)。 大家知道&#xff0c;在所有计算机系统的设计中…
最新文章