基于SpringBoot的Ftp连接池终极实现

有时候,不是因为你没有能力,也不是因为你缺少勇气,只是因为你付出的努力还太少,所以,成功便不会走向你。而你所需要做的,就是坚定你的梦想,你的目标,你的未来,然后以不达目的誓不罢休的那股劲,去付出你的努力,成功就会慢慢向你靠近。

导读:本篇文章讲解 基于SpringBoot的Ftp连接池终极实现,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

1、配置文件

package com.faea.bus.core.properties;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author liuchao
 * @date 2020-03-28
 */
@Getter
@Setter
@ConfigurationProperties(prefix = FtpProperties.PREFIX, ignoreUnknownFields = false)
public class FtpProperties {
    public static final String PREFIX = "faea.ftp";

    /**
     * Ip
     */
    private String host;

    /**
     * 端口
     */
    private Integer port;

    /**
     * 登录账号
     */
    private String name;

    /**
     * 登录密码
     */
    private String password;

    /**
     * 访问前缀
     */
    private String urlPrefix;
    /**
     * 是否被动模式
     */
    private boolean passiveMode = false;
    /**
     * 编码格式
     */
    private String encoding = "UTF-8";
    /**
     * 连接超时时间
     */
    private int connectTimeout = 30000;
    /**
     * 缓存
     */
    private int bufferSize = 8096;
}

2、基于Apache commons-pool2  对象池实现ftpClient 池工厂

package com.faea.bus.core.ftp;

import cn.hutool.core.util.StrUtil;
import com.faea.bus.core.exception.FaeaBusinessException;
import com.faea.bus.core.properties.FtpProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;

import java.io.IOException;

/**
 * Ftp客户端工厂
 *
 * @author liuchao
 * @date 2020/6/28
 */
@Slf4j
public class FtpClientFactory extends BasePooledObjectFactory<FTPClient> {
    private FtpProperties ftpProperties;

    public FtpClientFactory(FtpProperties ftpProperties) {
        this.ftpProperties = ftpProperties;
    }

    @Override
    public FTPClient create() {
        final FTPClient ftpClient = new FTPClient();
        ftpClient.setControlEncoding(ftpProperties.getEncoding());
        ftpClient.setConnectTimeout(ftpProperties.getConnectTimeout());
        try {
            // 连接ftp服务器
            ftpClient.connect(ftpProperties.getHost(), ftpProperties.getPort());
            // 登录ftp服务器
            ftpClient.login(ftpProperties.getName(), ftpProperties.getPassword());
        } catch (IOException e) {
            throw new FaeaBusinessException("创建ftp连接失败", e);
        }
        // 是否成功登录服务器
        final int replyCode = ftpClient.getReplyCode();
        if (!FTPReply.isPositiveCompletion(replyCode)) {
            try {
                ftpClient.disconnect();
            } catch (IOException e) {
                // ignore
            }
            throw new FaeaBusinessException(StrUtil.format("Login failed for user [{}], reply code is: [{}]",
                    ftpProperties.getName(), replyCode));
        }
        ftpClient.setBufferSize(ftpProperties.getBufferSize());
        //设置模式
        if (ftpProperties.isPassiveMode()) {
            ftpClient.enterLocalPassiveMode();
        }
        return ftpClient;
    }

    @Override
    public PooledObject<FTPClient> wrap(FTPClient obj) {
        return new DefaultPooledObject<>(obj);
    }

    /**
     * 销毁FtpClient对象
     */
    @Override
    public void destroyObject(PooledObject<FTPClient> ftpPooled) {
        if (ftpPooled == null) {
            return;
        }
        FTPClient ftpClient = ftpPooled.getObject();

        try {
            if (ftpClient.isConnected()) {
                ftpClient.logout();
            }
        } catch (IOException io) {
            log.error("ftp client logout failed...{}", io);
        } finally {
            try {
                ftpClient.disconnect();
            } catch (IOException io) {
                log.error("close ftp client failed...{}", io);
            }
        }
    }

    /**
     * 验证FtpClient对象是否还可用
     */
    @Override
    public boolean validateObject(PooledObject<FTPClient> ftpPooled) {
        try {
            FTPClient ftpClient = ftpPooled.getObject();
            return ftpClient.sendNoOp();
        } catch (IOException e) {
            log.error("Failed to validate client: {}", e);
        }
        return false;
    }

}

3、ftpClient 池

package com.faea.bus.core.ftp;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.BaseObjectPool;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * ftp client 池
 *
 * @author liuchao
 * @date 2020/6/28
 */
@Slf4j
public class FtpClientPool extends BaseObjectPool<FTPClient> {
    private static final int DEFAULT_POOL_SIZE = 4;

    private final BlockingQueue<FTPClient> ftpBlockingQueue;
    private final FtpClientFactory ftpClientFactory;

    /**
     * 初始化连接池,需要注入一个工厂来提供FTPClient实例
     *
     * @param ftpClientFactory ftp工厂
     * @throws Exception
     */
    public FtpClientPool(FtpClientFactory ftpClientFactory) throws Exception {
        this(DEFAULT_POOL_SIZE, ftpClientFactory);
    }

    public FtpClientPool(int poolSize, FtpClientFactory factory) throws Exception {
        this.ftpClientFactory = factory;
        ftpBlockingQueue = new ArrayBlockingQueue<>(poolSize);
        initPool(poolSize);
    }

    /**
     * 初始化连接池,需要注入一个工厂来提供FTPClient实例
     *
     * @param maxPoolSize 最大连接数
     * @throws Exception
     */
    private void initPool(int maxPoolSize) throws Exception {
        for (int i = 0; i < maxPoolSize; i++) {
            // 往池中添加对象
            addObject();
        }
    }

    /**
     * 获取连接
     *
     * @return
     * @throws Exception
     */
    @Override
    public FTPClient borrowObject() throws Exception {
        FTPClient client = ftpBlockingQueue.take();
        if (ObjectUtils.isEmpty(client)) {
            client = ftpClientFactory.create();
            // 放入连接池
            returnObject(client);
            // 验证对象是否有效  这里通过实践验证 如果长时间不校验是否存活,则这里会报通道已断开等错误
        } else if (!ftpClientFactory.validateObject(ftpClientFactory.wrap(client))) {
            // 对无效的对象进行处理
            invalidateObject(client);
            // 创建新的对象
            client = ftpClientFactory.create();
            // 将新的对象放入连接池
            returnObject(client);
        }
        return client;
    }

    @Override
    public void returnObject(FTPClient client) {
        try {
            if (client != null && !ftpBlockingQueue.offer(client, 3, TimeUnit.SECONDS)) {
                ftpClientFactory.destroyObject(ftpClientFactory.wrap(client));
            }
        } catch (InterruptedException e) {
            log.error("return ftp client interrupted ...{}", e);
        }
    }

    @Override
    public void invalidateObject(FTPClient client) {
        try {
            if (client.isConnected()) {
                client.logout();
            }
        } catch (IOException io) {
            log.error("ftp client logout failed...{}", io);
        } finally {
            try {
                client.disconnect();
            } catch (IOException io) {
                log.error("close ftp client failed...{}", io);
            }
            ftpBlockingQueue.remove(client);
        }
    }

    /**
     * 增加一个新的链接,超时失效
     */
    @Override
    public void addObject() throws Exception {
        // 插入对象到队列
        ftpBlockingQueue.offer(ftpClientFactory.create(), 3, TimeUnit.SECONDS);
    }

    /**
     * 关闭连接池
     */
    @Override
    public void close() {
        try {
            while (ftpBlockingQueue.iterator().hasNext()) {
                FTPClient client = ftpBlockingQueue.take();
                ftpClientFactory.destroyObject(ftpClientFactory.wrap(client));
            }
        } catch (Exception e) {
            log.error("close ftp client ftpBlockingQueue failed...{}", e);
        }
    }

    public BlockingQueue<FTPClient> getFtpBlockingQueue() {
        return ftpBlockingQueue;
    }
}

4、基于Hutool ftp工具类源码实现接入ftp连接池抽象类

package com.faea.bus.core.ftp;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;

import java.io.File;
import java.util.List;

/**
 * @author liuchao
 * @date 2020/6/28
 */
public abstract class FaeaAbstractFtp {

    /**
     * 打开指定目录
     *
     * @param directory directory
     * @return 是否打开目录
     */
    public abstract boolean cd(String directory);

    /**
     * 打开上级目录
     *
     * @return 是否打开目录
     * @since 4.0.5
     */
    public boolean toParent() {
        return cd("..");
    }

    /**
     * 远程当前目录(工作目录)
     *
     * @return 远程当前目录
     */
    public abstract String pwd();

    /**
     * 在当前远程目录(工作目录)下创建新的目录
     *
     * @param dir 目录名
     * @return 是否创建成功
     */
    public abstract boolean mkdir(String dir);

    /**
     * 文件或目录是否存在
     *
     * @param path 目录
     * @return 是否存在
     */
    public boolean exist(String path) {
        final String fileName = FileUtil.getName(path);
        final String dir = StrUtil.removeSuffix(path, fileName);
        final List<String> names = ls(dir);
        return containsIgnoreCase(names, fileName);
    }

    /**
     * 遍历某个目录下所有文件和目录,不会递归遍历
     *
     * @param path 需要遍历的目录
     * @return 文件和目录列表
     */
    public abstract List<String> ls(String path);

    /**
     * 删除指定目录下的指定文件
     *
     * @param path 目录路径
     * @return 是否存在
     */
    public abstract boolean delFile(String path);

    /**
     * 删除文件夹及其文件夹下的所有文件
     *
     * @param dirPath 文件夹路径
     * @return boolean 是否删除成功
     */
    public abstract boolean delDir(String dirPath);

    /**
     * 创建指定文件夹及其父目录,从根目录开始创建,创建完成后回到默认的工作目录
     *
     * @param dir 文件夹路径,绝对路径
     */
    public void mkDirs(String dir) {
        final String[] dirs = StrUtil.trim(dir).split("[\\\\/]+");

        final String now = pwd();
        if (dirs.length > 0 && StrUtil.isEmpty(dirs[0])) {
            //首位为空,表示以/开头
            this.cd(StrUtil.SLASH);
        }
        for (int i = 0; i < dirs.length; i++) {
            if (StrUtil.isNotEmpty(dirs[i])) {
                if (false == cd(dirs[i])) {
                    //目录不存在时创建
                    mkdir(dirs[i]);
                    cd(dirs[i]);
                }
            }
        }
        // 切换回工作目录
        cd(now);
    }

    /**
     * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。覆盖模式
     *
     * @param srcFilePath 本地文件路径
     * @param destFile    目标文件
     * @return 是否成功
     */
    public abstract boolean upload(String srcFilePath, File destFile);

    /**
     * 下载文件
     *
     * @param path    文件路径
     * @param outFile 输出文件或目录
     */
    public abstract void download(String path, File outFile);

    // ---------------------------------------------------------------------------------------------------------------------------------------- Private method start

    /**
     * 是否包含指定字符串,忽略大小写
     *
     * @param names      文件或目录名列表
     * @param nameToFind 要查找的文件或目录名
     * @return 是否包含
     */
    private static boolean containsIgnoreCase(List<String> names, String nameToFind) {
        if (CollUtil.isEmpty(names)) {
            return false;
        }
        if (StrUtil.isEmpty(nameToFind)) {
            return false;
        }
        for (String name : names) {
            if (nameToFind.equalsIgnoreCase(name)) {
                return true;
            }
        }
        return false;
    }
    // ---------------------------------------------------------------------------------------------------------------------------------------- Private method end

    /**
     * 关闭连接
     *
     * @author liuchao
     * @date 2020/6/28
     */
    public abstract void close() throws Exception;
}

5、工具类抽象实现

package com.faea.bus.core.ftp;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.faea.bus.core.exception.FaeaBusinessException;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * Ftp 工具类实现
 *
 * @author liuchao
 * @date 2020/6/28
 */
public class FaeaFtp extends FaeaAbstractFtp {

    private FTPClient client;
    private FtpClientPool ftpClientPool;

    public FaeaFtp(FtpClientPool ftpClientPool, FTPClient client) {
        this.ftpClientPool = ftpClientPool;
        this.client = client;
    }

    public FaeaFtp(FtpClientPool ftpClientPool) throws Exception {
        this.ftpClientPool = ftpClientPool;
        this.client = ftpClientPool.borrowObject();
    }

    /**
     * 执行完操作是否返回当前目录
     */
    private boolean backToPwd;

    /**
     * 设置执行完操作是否返回当前目录
     *
     * @param backToPwd 执行完操作是否返回当前目录
     * @return this
     */
    public FaeaFtp setBackToPwd(boolean backToPwd) {
        this.backToPwd = backToPwd;
        return this;
    }

    /**
     * 改变目录
     *
     * @param directory 目录
     * @return 是否成功
     */
    @Override
    public boolean cd(String directory) {
        if (StrUtil.isBlank(directory)) {
            return false;
        }

        boolean flag;
        try {
            flag = client.changeWorkingDirectory(directory);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        }
        return flag;
    }

    /**
     * 远程当前目录
     *
     * @return 远程当前目录
     */
    @Override
    public String pwd() {
        try {
            return client.printWorkingDirectory();
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        }
    }

    @Override
    public List<String> ls(String path) {
        final FTPFile[] ftpFiles = lsFiles(path);

        final List<String> fileNames = new ArrayList<>();
        for (FTPFile ftpFile : ftpFiles) {
            fileNames.add(ftpFile.getName());
        }
        return fileNames;
    }

    /**
     * 遍历某个目录下所有文件和目录,不会递归遍历
     *
     * @param path 目录
     * @return 文件或目录列表
     */
    public FTPFile[] lsFiles(String path) {
        String pwd = null;
        if (StrUtil.isNotBlank(path)) {
            pwd = pwd();
            cd(path);
        }

        FTPFile[] ftpFiles;
        try {
            ftpFiles = this.client.listFiles();
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        } finally {
            // 回到原目录
            cd(pwd);
        }

        return ftpFiles;
    }

    @Override
    public boolean mkdir(String dir) {
        boolean flag = true;
        try {
            flag = this.client.makeDirectory(dir);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        }
        return flag;
    }

    /**
     * 判断ftp服务器文件是否存在
     *
     * @param path 文件路径
     * @return 是否存在
     */
    public boolean existFile(String path) {
        FTPFile[] ftpFileArr;
        try {
            ftpFileArr = client.listFiles(path);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        }
        if (ArrayUtil.isNotEmpty(ftpFileArr)) {
            return true;
        }
        return false;
    }

    @Override
    public boolean delFile(String path) {
        final String pwd = pwd();
        final String fileName = FileUtil.getName(path);
        final String dir = StrUtil.removeSuffix(path, fileName);
        cd(dir);
        boolean isSuccess;
        try {
            isSuccess = client.deleteFile(fileName);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        } finally {
            // 回到原目录
            cd(pwd);
        }
        return isSuccess;
    }

    @Override
    public boolean delDir(String dirPath) {
        FTPFile[] dirs;
        try {
            dirs = client.listFiles(dirPath);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        }
        String name;
        String childPath;
        for (FTPFile ftpFile : dirs) {
            name = ftpFile.getName();
            childPath = StrUtil.format("{}/{}", dirPath, name);
            if (ftpFile.isDirectory()) {
                // 上级和本级目录除外
                if (false == name.equals(".") && false == name.equals("..")) {
                    delDir(childPath);
                }
            } else {
                delFile(childPath);
            }
        }

        // 删除空目录
        try {
            return this.client.removeDirectory(dirPath);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        }
    }

    /**
     * 上传文件到指定目录,可选:
     *
     * <pre>
     * 1. path为null或""上传到当前路径
     * 2. path为相对路径则相对于当前路径的子路径
     * 3. path为绝对路径则上传到此路径
     * </pre>
     *
     * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径
     * @param file 文件
     * @return 是否上传成功
     */
    @Override
    public boolean upload(String path, File file) {
        Assert.notNull(file, "file to upload is null !");
        return upload(path, file.getName(), file);
    }

    /**
     * 上传文件到指定目录,可选:
     *
     * <pre>
     * 1. path为null或""上传到当前路径
     * 2. path为相对路径则相对于当前路径的子路径
     * 3. path为绝对路径则上传到此路径
     * </pre>
     *
     * @param file     文件
     * @param path     服务端路径,可以为{@code null} 或者相对路径或绝对路径
     * @param fileName 自定义在服务端保存的文件名
     * @return 是否上传成功
     */
    public boolean upload(String path, String fileName, File file) {
        try (InputStream in = FileUtil.getInputStream(file)) {
            return upload(path, fileName, in);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        }
    }

    /**
     * 上传文件到指定目录,可选:
     *
     * <pre>
     * 1. path为null或""上传到当前路径
     * 2. path为相对路径则相对于当前路径的子路径
     * 3. path为绝对路径则上传到此路径
     * </pre>
     *
     * @param path       服务端路径,可以为{@code null} 或者相对路径或绝对路径
     * @param fileName   文件名
     * @param fileStream 文件流
     * @return 是否上传成功
     */
    public boolean upload(String path, String fileName, InputStream fileStream) {
        try {
            client.setFileType(FTPClient.BINARY_FILE_TYPE);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        }

        String pwd = null;
        if (this.backToPwd) {
            pwd = pwd();
        }

        if (StrUtil.isNotBlank(path)) {
            mkDirs(path);
            boolean isOk = cd(path);
            if (false == isOk) {
                return false;
            }
        }

        try {
            return client.storeFile(fileName, fileStream);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        } finally {
            if (this.backToPwd) {
                cd(pwd);
            }
        }
    }

    /**
     * 下载文件
     *
     * @param path    文件路径
     * @param outFile 输出文件或目录
     */
    @Override
    public void download(String path, File outFile) {
        final String fileName = FileUtil.getName(path);
        final String dir = StrUtil.removeSuffix(path, fileName);
        download(dir, fileName, outFile);
    }

    /**
     * 下载文件
     *
     * @param path     文件路径
     * @param fileName 文件名
     * @param outFile  输出文件或目录
     */
    public void download(String path, String fileName, File outFile) {
        if (outFile.isDirectory()) {
            outFile = new File(outFile, fileName);
        }
        if (false == outFile.exists()) {
            FileUtil.touch(outFile);
        }
        try (OutputStream out = FileUtil.getOutputStream(outFile)) {
            download(path, fileName, out);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        }
    }

    /**
     * 下载文件到输出流
     *
     * @param path     文件路径
     * @param fileName 文件名
     * @param out      输出位置
     */
    public void download(String path, String fileName, OutputStream out) {
        String pwd = null;
        if (this.backToPwd) {
            pwd = pwd();
        }
        cd(path);
        try {
            client.setFileType(FTPClient.BINARY_FILE_TYPE);
            client.retrieveFile(fileName, out);
        } catch (IOException e) {
            throw new FaeaBusinessException(e);
        } finally {
            if (backToPwd) {
                cd(pwd);
            }
        }
    }

    @Override
    public void close() {
        ftpClientPool.returnObject(client);
    }

    public void setClient(FTPClient client) {
        this.client = client;
    }
}

6、Ftp如果长时间不校验存活,再从连接池中获取到的client调用校验方法会报错,需要有一个心跳不断的校验是否存活,这里实现用了一个线程

package com.faea.bus.core.ftp;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PostConstruct;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;

/**
 * 检测ftp客户端是否在活着
 *
 * @author liuchao
 * @date 2020/6/30
 */
@Slf4j
public class FtpClientKeepAlive {

    private KeepAliveThread keepAliveThread;

    @Autowired
    private FtpClientPool ftpClientPool;

    private final String THREAD_NAME = "ftp-client-alive-thread";

    @PostConstruct
    public void init() {
        // 启动心跳检测线程
        if (keepAliveThread == null) {
            keepAliveThread = new KeepAliveThread();
            Thread thread = new Thread(keepAliveThread, THREAD_NAME);
            thread.start();
        }
    }

    class KeepAliveThread implements Runnable {
        @Override
        public void run() {
            FTPClient ftpClient = null;
            while (true) {
                try {
                    BlockingQueue<FTPClient> pool = ftpClientPool.getFtpBlockingQueue();
                    if (pool != null && pool.size() > 0) {
                        Iterator<FTPClient> it = pool.iterator();
                        while (it.hasNext()) {
                            ftpClient = it.next();
                            boolean result = ftpClient.sendNoOp();
                            if (log.isDebugEnabled()) {
                                log.info("心跳结果: {}", result);
                            }
                            if (!result) {
                                ftpClientPool.invalidateObject(ftpClient);
                            }
                        }

                    }
                } catch (Exception e) {
                    log.error("ftp心跳检测异常", e);
                    ftpClientPool.invalidateObject(ftpClient);
                }
                // 每30s发送一次心跳
                try {
                    Thread.sleep(1000 * 30);
                } catch (InterruptedException e) {
                    log.error("ftp休眠异常", e);
                }
            }

        }
    }
}

7、SpringBoot 自动化配置

package com.faea.bus.core.config;

import com.faea.bus.core.ftp.FtpClientFactory;
import com.faea.bus.core.ftp.FtpClientKeepAlive;
import com.faea.bus.core.ftp.FtpClientPool;
import com.faea.bus.core.properties.FtpProperties;
import com.faea.bus.core.utils.FaeaFtpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

/**
 * @author liuchao
 * @date 2020/6/28
 */
@Slf4j
@EnableConfigurationProperties(FtpProperties.class)
public class FtpConfig {
    @Autowired
    FtpProperties ftpProperties;

    /**
     * 客户端工厂
     *
     * @return
     */
    @Bean
    public FtpClientFactory ftpClientFactory() {
        return new FtpClientFactory(ftpProperties);
    }

    /**
     * 连接池
     *
     * @param ftpClientFactory
     * @return
     * @throws Exception
     */
    @Bean
    public FtpClientPool ftpClientPool(FtpClientFactory ftpClientFactory) throws Exception {
        return new FtpClientPool(ftpClientFactory);
    }

    /**
     * ftp 工具类
     */
    @Bean
    @ConditionalOnMissingBean
    public FaeaFtpUtil faeaFtpUtil() {
        return new FaeaFtpUtil();
    }

    /**
     * 检测ftp是否在活着
     */
    @Bean
    @ConditionalOnBean(FtpClientPool.class)
    public FtpClientKeepAlive ftpClientKeepAlive() {
        return new FtpClientKeepAlive();
    }

}

8、在需要使用的项目中启用ftp,这里通过注解实现,在需要使用的应用的配置类中引入这个注解即可

package com.faea.bus.core.annotation;

import com.faea.bus.core.config.FtpConfig;
import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 启用Ftp自动配置
 *
 * @author liuchao
 * @date 2020/6/28
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(FtpConfig.class)
public @interface EnableFtp {
}

9、最后来一个使用的工具类

package com.faea.bus.core.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.faea.bus.core.constants.FaeaBusConstant;
import com.faea.bus.core.exception.FaeaBusinessException;
import com.faea.bus.core.ftp.FaeaFtp;
import com.faea.bus.core.ftp.FtpClientPool;
import com.faea.core.collections.SynchronizedStack;
import com.faea.core.utils.FaeaStringPool;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.tika.mime.MimeTypeException;
import org.apache.tika.mime.MimeTypes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * ftp 工具类
 *
 * @author liuchao
 * @date 2020/6/28
 */
public class FaeaFtpUtil {

    @Autowired
    FtpClientPool ftpClientPool;
    @Value("${spring.profiles.active}")
    private String activeProfile;

    private final MimeTypes allTypes = MimeTypes.getDefaultMimeTypes();

    /**
     * 缓存FaeaFtp 对象消除 FaeaFtp频繁创建和销毁丢失的性能
     */
    private SynchronizedStack<FaeaFtp> faeaFtpCache;

    public FaeaFtpUtil() {
        this.faeaFtpCache = new SynchronizedStack<>();
    }

    /**
     * 获取ftp
     *
     * @return com.faea.bus.core.ftp.FaeaFtp
     * @author liuchao
     * @date 2020/6/28
     */
    private FaeaFtp getFtp() {
        try {
            FaeaFtp ftp = faeaFtpCache.pop();
            FTPClient client = ftpClientPool.borrowObject();
            if (ObjectUtil.isEmpty(ftp)) {
                ftp = new FaeaFtp(ftpClientPool, client);
            } else {
                ftp.setClient(client);
            }
            //使用完需要还原到原来目录
            ftp.setBackToPwd(Boolean.TRUE);
            return ftp;
        } catch (Exception e) {
            throw new FaeaBusinessException("获取ftp连接失败", e);
        }
    }

    /**
     * 通过MultipartFile上传文件
     *
     * @param typeEnum 类型
     * @param file     文件
     * @return java.lang.String
     * @author liuchao
     * @date 2020/6/10
     */
    public String upload(FaeaBusConstant.FtpUploadTypeEnum typeEnum, MultipartFile file) {
        try {
            String originalFileName = file.getOriginalFilename();
            String fileExtendName = originalFileName.substring(originalFileName.lastIndexOf(FaeaStringPool.DOT));
            return upload(typeEnum, file.getInputStream(), fileExtendName);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 文件上传
     *
     * @param typeEnum       上传类型
     * @param is             流程
     * @param fileExtendName 扩展名称 eg:'.jpg'
     * @return java.lang.String
     * @author liuchao
     * @date 2020/6/28
     */
    public String upload(FaeaBusConstant.FtpUploadTypeEnum typeEnum, InputStream is, String fileExtendName) {
        FaeaFtp ftp = getFtp();
        String path = typeEnum.getDir() + activeProfile + File.separator
                + DateUtil.date().toDateStr();
        String fileName = IdUtil.fastSimpleUUID() + fileExtendName;
        try {
            return ftp.upload(path, fileName, is) ? path + File.separator + fileName : null;
        } finally {
            ftp.close();
            faeaFtpCache.push(ftp);
            IoUtil.close(is);
        }
    }


    /**
     * Ftp上传文件
     *
     * @param typeEnum 上传类型
     * @param strUrl   文件网络路径
     * @return java.lang.String
     * @author liuchao
     * @date 2020-03-27
     */
    public String upload(FaeaBusConstant.FtpUploadTypeEnum typeEnum, String strUrl) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(strUrl);
            connection = (HttpURLConnection) url.openConnection();
            connection.connect();
            BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
            return upload(typeEnum, is, getFileExtName(connection.getContentType()));
        } catch (MimeTypeException e) {
            throw new FaeaBusinessException("获取上传文件类型失败");
        } catch (IOException e) {
            throw new FaeaBusinessException("上传文件失败");
        } finally {
            if (ObjectUtil.isNotEmpty(connection)) {
                connection.disconnect();
            }
        }
    }

    /**
     * 获取流文件扩展名
     *
     * @param contentType
     * @return java.lang.String
     * @author liuchao
     * @date 2020/6/28
     */
    public String getFileExtName(String contentType) throws MimeTypeException {
        return allTypes.forName(contentType).getExtension();
    }

}

10、中间使用到的依赖

  • spring-boot

  • hutool

  • lombok

  • apache commons-pool2 

  • com.faea 包属于我当前项目,可以相应的更改哈,我有点懒

11、优化版本,经过长期生产实践,单独整理项目供大家下载使用

SpringBoot2.2+commons-pool2实现多Ftp连接池完整项目,开箱即用,经过长期生产使用稳定可靠_Springboot整合commons-pool2-Java文档类资源-CSDN下载使用JDK1.8、SpringBoot2.2.10.RELEASE、lombok1.18.8、guaSpringboot整合commons-pool2更多下载资源、学习资料请访问CSDN下载频道.基于SpringBoot的Ftp连接池终极实现https://download.csdn.net/download/u011837804/85074791

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/144727.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!