基於SpringBoot的Ftp連接池終極實現

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、在使用過程有問題歡迎留言

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章