1. 程式人生 > >springboot快速啟動外掛ftp篇-連線池

springboot快速啟動外掛ftp篇-連線池

功能點:

  • 初始化ftp連線池​​​​​​​
  • 封裝ftp基礎操作

1.建立maven工程

2.編寫pom檔案

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>iotstarter</artifactId>
        <groupId>com.iot</groupId>
        <version>0.0.1-RELEASES</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <groupId>com.iot</groupId>
    <artifactId>iot-ftp-spring-boot-starter</artifactId>
    <version>0.0.1-RELEASES</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>commons-net</groupId>
            <artifactId>commons-net</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
    </dependencies>
</project>

3.編寫配置實體(讀取配置檔案資訊)

package com.iot.ftp.properties;

import lombok.Data;
import org.apache.commons.net.ftp.FTPClient;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "iot-ftp")
@Data
public class FtpOptionProperties {
    private String host;
    private int port= FTPClient.DEFAULT_PORT;
    private String username;
    private String password;
    private int bufferSize = 8096;
    /**
     * 初始化連線數
     */
    private Integer initialSize = 0;
    private String encoding;


}

4.編寫總配置類(初始化單例bean)

package com.iot.ftp.config;

import com.iot.ftp.properties.FtpOptionProperties;
import com.iot.ftp.service.IotFtpService;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PreDestroy;

/**
 * 描述: els快捷操作配置
 * @author: myx
 * @date: 2018/5/3
 * Copyright © 2018-hotpot. All rights reserved.
 */
@Configuration//開啟配置
@EnableConfigurationProperties(FtpOptionProperties.class)//開啟使用對映實體物件
@ConditionalOnClass({IotFtpService.class,GenericObjectPool.class, FTPClient.class})//存在IotFtpService時初始化該配置類
@ConditionalOnProperty//存在對應配置資訊時初始化該配置類
        (
                prefix = "iot-ftp",//存在配置字首hello
                name = "isopen",
                havingValue = "true",//開啟
                matchIfMissing = true//缺失檢查
        )
public class FtpConfiguration {
    private Logger log = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private FtpOptionProperties ftpOptionProperties;

    private ObjectPool<FTPClient> pool;

    /**
     * 預先載入FTPClient連線到物件池中
     * @param initialSize 初始化連線數
     * @param maxIdle 最大空閒連線數
     */
    private void preLoadingFtpClient(Integer initialSize, int maxIdle) {
        if (initialSize == null || initialSize <= 0) {
            return;
        }

        int size = Math.min(initialSize.intValue(), maxIdle);
        for (int i = 0; i < size; i++) {
            try {
                pool.addObject();
            } catch (Exception e) {
                log.error("preLoadingFtpClient error...", e);
            }
        }
    }
    @PreDestroy
    public void destroy() {
        if (pool != null) {
            pool.close();
            log.info("銷燬ftpClientPool...");
        }
    }
    /**
     * 根據條件判斷不存在ElsService時初始化新bean到SpringIoc
     * @return
     */
    @Bean//建立HelloService實體bean
    @ConditionalOnMissingBean(IotFtpService.class)//缺失HelloService實體bean時,初始化HelloService並新增到SpringIoc
    public IotFtpService iotFtpService()
            throws Exception{
        log.info(">>>The IotFtpService Not Found,Execute Creat New Bean.");
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setTestOnBorrow(true);
        poolConfig.setTestOnReturn(true);
        poolConfig.setTestWhileIdle(true);
        poolConfig.setMinEvictableIdleTimeMillis(60000);
        poolConfig.setSoftMinEvictableIdleTimeMillis(50000);
        poolConfig.setTimeBetweenEvictionRunsMillis(30000);
        pool = new GenericObjectPool<>(new FtpClientPooledObjectFactory(ftpOptionProperties), poolConfig);
        preLoadingFtpClient(ftpOptionProperties.getInitialSize(), poolConfig.getMaxIdle());
        IotFtpService iotFtpService =new IotFtpService();
        iotFtpService.setFtpClientPool(pool);
        iotFtpService.setHasInit(true);
        return iotFtpService;
    }


    /**
     * FtpClient物件工廠類
     */
    static class FtpClientPooledObjectFactory implements PooledObjectFactory<FTPClient> {
        private Logger log = LoggerFactory.getLogger(this.getClass());

        private FtpOptionProperties props;

        public FtpClientPooledObjectFactory(FtpOptionProperties props) {
            this.props = props;
        }

        @Override
        public PooledObject<FTPClient> makeObject() throws Exception {
            FTPClient ftpClient = new FTPClient();
            try {
                ftpClient.connect(props.getHost(), props.getPort());
                ftpClient.login(props.getUsername(), props.getPassword());
                log.info("連線FTP伺服器返回碼{}", ftpClient.getReplyCode());
                ftpClient.setBufferSize(props.getBufferSize());
                ftpClient.setControlEncoding(props.getEncoding());
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                ftpClient.enterLocalPassiveMode();
                return new DefaultPooledObject<>(ftpClient);
            } catch (Exception e) {
                log.error("建立FTP連線失敗", e);
                if (ftpClient.isAvailable()) {
                    ftpClient.disconnect();
                }
                ftpClient = null;
                throw new Exception("建立FTP連線失敗", e);
            }
        }

        @Override
        public void destroyObject(PooledObject<FTPClient> p) throws Exception {
            FTPClient ftpClient = getObject(p);
            if (ftpClient != null && ftpClient.isConnected()) {
                ftpClient.disconnect();
            }
        }

        @Override
        public boolean validateObject(PooledObject<FTPClient> p) {
            FTPClient ftpClient = getObject(p);
            if (ftpClient == null || !ftpClient.isConnected()) {
                return false;
            }
            try {
                ftpClient.changeWorkingDirectory("/");
                return true;
            } catch (Exception e) {
                log.error("驗證FTP連線失敗::{}",e.getMessage());
                return false;
            }
        }

        @Override
        public void activateObject(PooledObject<FTPClient> p) throws Exception {
        }

        @Override
        public void passivateObject(PooledObject<FTPClient> p) throws Exception {
        }

        private FTPClient getObject(PooledObject<FTPClient> p) {
            if (p == null || p.getObject() == null) {
                return null;
            }
            return p.getObject();
        }

    }
}

5.編寫ftp操作類(核心)

package com.iot.ftp.service;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.pool2.ObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 描述:ftp服務端
 * @author: myx
 * @date: 2018/5/3
 * Copyright © 2018-hotpot. All rights reserved.
 */
@Data
public class IotFtpService {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * ftpClient連線池初始化標誌
     */
    private boolean hasInit = false;
    /**
     * ftpClient連線池
     */
    private ObjectPool<FTPClient> ftpClientPool;

    /**
     * 上傳檔案
     * @param pathname ftp服務儲存地址
     * @param fileName 上傳到ftp的檔名
     *  @param originfilename 待上傳檔案的名稱(絕對地址) *
     * @return
     */
    public boolean uploadFile( String pathname, String fileName,String originfilename){
        boolean flag = false;
        InputStream inputStream = null;
        FTPClient ftpClient = getFtpClient();
        try{
            log.info("開始上傳檔案");
            inputStream = new FileInputStream(new File(originfilename));
            ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE);
            CreateDirecroty(pathname,ftpClient);
            ftpClient.makeDirectory(pathname);
            ftpClient.changeWorkingDirectory(pathname);
            ftpClient.storeFile(fileName, inputStream);
            inputStream.close();
            flag = true;
            log.info("上傳檔案成功");
        }catch (Exception e) {
            log.error("上傳檔案失敗");
            e.printStackTrace();
        }finally{
            releaseFtpClient(ftpClient);
        }
        return flag;
    }


    /**
     * 上傳檔案
     * @param pathname ftp服務儲存地址
     * @param fileName 上傳到ftp的檔名
     * @param inputStream 輸入檔案流
     * @return
     */
    public boolean uploadFile( String pathname, String fileName,InputStream inputStream){
        boolean flag = false;
        FTPClient ftpClient = getFtpClient();
        try{
            log.info("開始上傳檔案");
            ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE);
            CreateDirecroty(pathname,ftpClient);
            ftpClient.makeDirectory(pathname);
            ftpClient.changeWorkingDirectory(pathname);
            ftpClient.storeFile(fileName, inputStream);
            inputStream.close();
            flag = true;
            log.info("上傳檔案成功");
        }catch (Exception e) {
            log.error("上傳檔案失敗");
            e.printStackTrace();
        }finally{
            releaseFtpClient(ftpClient);
        }
        return flag;
    }

    /** * 下載檔案 *
     * @param pathname FTP伺服器檔案目錄 *
     * @param filename 檔名稱 *
     * @param localpath 下載後的檔案路徑 *
     * @return */
    public  boolean downloadFile(String pathname, String filename, String localpath){
        boolean flag = false;
        OutputStream os=null;
        FTPClient ftpClient = getFtpClient();
        try {
            log.info("開始下載檔案");
            //切換FTP目錄
            ftpClient.changeWorkingDirectory(pathname);
            FTPFile[] ftpFiles = ftpClient.listFiles();
            for(FTPFile file : ftpFiles){
                if(filename.equalsIgnoreCase(file.getName())){
                    File localFile = new File(localpath + "/" + file.getName());
                    os = new FileOutputStream(localFile);
                    ftpClient.retrieveFile(file.getName(), os);
                    os.close();
                }
            }
            flag = true;
            log.info("下載檔案成功");
        } catch (Exception e) {
            log.error("下載檔案失敗");
            e.printStackTrace();
        } finally{
            releaseFtpClient(ftpClient);
            if(null != os){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return flag;
    }

    /** * 刪除檔案 *
     * @param pathname FTP伺服器儲存目錄 *
     * @param filename 要刪除的檔名稱 *
     * @return */
    public boolean deleteFile(String pathname, String filename){
        boolean flag = false;
        FTPClient ftpClient = getFtpClient();
        try {
            log.info("開始刪除檔案");
            //切換FTP目錄
            ftpClient.changeWorkingDirectory(pathname);
            ftpClient.dele(filename);
            ftpClient.logout();
            flag = true;
            log.info("刪除檔案成功");
        } catch (Exception e) {
            log.error("刪除檔案失敗");
            e.printStackTrace();
        } finally {
            releaseFtpClient(ftpClient);
        }
        return flag;
    }

    /**
     * 按行讀取FTP檔案
     *
     * @param remoteFilePath 檔案路徑(path+fileName)
     * @return
     * @throws IOException
     */
    public List<String> readFileByLine(String remoteFilePath) throws IOException {
        FTPClient ftpClient = getFtpClient();
        try (InputStream in = ftpClient.retrieveFileStream(encodingPath(remoteFilePath));
             BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
            return br.lines().map(line -> StrUtil.trimToEmpty(line))
                    .filter(line -> StrUtil.isNotEmpty(line)).collect(Collectors.toList());
        } finally {
            ftpClient.completePendingCommand();
            releaseFtpClient(ftpClient);
        }
    }

    /**
     * 獲取指定路徑下FTP檔案
     *
     * @param remotePath 路徑
     * @return FTPFile陣列
     * @throws IOException
     */
    public FTPFile[] retrieveFTPFiles(String remotePath) throws IOException {
        FTPClient ftpClient = getFtpClient();
        try {
            return ftpClient.listFiles(encodingPath(remotePath + "/"),
                    file -> file != null && file.getSize() > 0);
        } finally {
            releaseFtpClient(ftpClient);
        }
    }

    /**
     * 獲取指定路徑下FTP檔名稱
     *
     * @param remotePath 路徑
     * @return ftp檔名稱列表
     * @throws IOException
     */
    public List<String> retrieveFileNames(String remotePath) throws IOException {
        FTPFile[] ftpFiles = retrieveFTPFiles(remotePath);
        if (ArrayUtil.isEmpty(ftpFiles)) {
            return new ArrayList<>();
        }
        return Arrays.stream(ftpFiles).filter(Objects::nonNull)
                .map(FTPFile::getName).collect(Collectors.toList());
    }

    /**
     * 編碼檔案路徑
     */
    private static String encodingPath(String path) throws UnsupportedEncodingException {
        // FTP協議裡面,規定檔名編碼為iso-8859-1,所以目錄名或檔名需要轉碼
        return new String(path.replaceAll("//", "/").getBytes("GBK"), "iso-8859-1");
    }

    /**
     * 獲取ftpClient
     *
     * @return
     */
    private FTPClient getFtpClient() {
        checkFtpClientPoolAvailable();
        FTPClient ftpClient = null;
        Exception ex = null;
        // 獲取連線最多嘗試3次
        for (int i = 0; i < 3; i++) {
            try {
                ftpClient = ftpClientPool.borrowObject();
                ftpClient.enterLocalPassiveMode();//被動模式
                ftpClient.changeWorkingDirectory("/");
                break;
            } catch (Exception e) {
                ex = e;
            }
        }
        if (ftpClient == null) {
            throw new RuntimeException("Could not get a ftpClient from the pool", ex);
        }
        return ftpClient;
    }

    /**
     * 釋放ftpClient
     */
    private void releaseFtpClient(FTPClient ftpClient) {
        if (ftpClient == null) {
            return;
        }

        try {
            ftpClientPool.returnObject(ftpClient);
        } catch (Exception e) {
            log.error("Could not return the ftpClient to the pool", e);
            // destoryFtpClient
            if (ftpClient.isAvailable()) {
                try {
                    ftpClient.disconnect();
                } catch (IOException io) {
                }
            }
        }
    }

    /**
     * 檢查ftpClientPool是否可用
     */
    private void checkFtpClientPoolAvailable() {
        Assert.state(hasInit, "FTP未啟用或連線失敗!");
    }


    /**
     * 建立多層目錄檔案,如果有ftp伺服器已存在該檔案,則不建立,如果無,則建立
     * @param remote
     * @param ftpClient
     * @return
     * @throws IOException
     */
    public boolean CreateDirecroty(String remote,FTPClient ftpClient) throws IOException {
        boolean success = true;
        String directory = remote + "/";
        // 如果遠端目錄不存在,則遞迴建立遠端伺服器目錄
        if (!directory.equalsIgnoreCase("/") && !changeWorkingDirectory(new String(directory),ftpClient)) {
            int start = 0;
            int end = 0;
            if (directory.startsWith("/")) {
                start = 1;
            } else {
                start = 0;
            }
            end = directory.indexOf("/", start);
            String path = "";
            String paths = "";
            while (true) {
                String subDirectory = new String(remote.substring(start, end).getBytes("GBK"), "iso-8859-1");
                path = path + "/" + subDirectory;
                if (!existFile(path,ftpClient)) {
                    if (makeDirectory(subDirectory,ftpClient)) {
                        changeWorkingDirectory(subDirectory,ftpClient);
                    } else {
                        System.out.println("建立目錄[" + subDirectory + "]失敗");
                        changeWorkingDirectory(subDirectory,ftpClient);
                    }
                } else {
                    changeWorkingDirectory(subDirectory,ftpClient);
                }

                paths = paths + "/" + subDirectory;
                start = end + 1;
                end = directory.indexOf("/", start);
                // 檢查所有目錄是否建立完畢
                if (end <= start) {
                    break;
                }
            }
        }
        return success;
    }

    /**
     * 改變目錄路徑
     * @param directory
     * @param ftpClient
     * @return
     */
    public boolean changeWorkingDirectory(String directory,FTPClient ftpClient) {
        boolean flag = true;
        try {
            flag = ftpClient.changeWorkingDirectory(directory);
            if (flag) {
                System.out.println("進入資料夾" + directory + " 成功!");

            } else {
                System.out.println("進入資料夾" + directory + " 失敗!開始建立資料夾");
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        return flag;
    }

    //判斷ftp伺服器檔案是否存在
    public boolean existFile(String path,FTPClient ftpClient) throws IOException {
        boolean flag = false;
        FTPFile[] ftpFileArr = ftpClient.listFiles(path);
        if (ftpFileArr.length > 0) {
            flag = true;
        }
        return flag;
    }
    //建立目錄
    public boolean makeDirectory(String dir,FTPClient ftpClient) {
        boolean flag = true;
        try {
            flag = ftpClient.makeDirectory(dir);
            if (flag) {
                log.info("建立資料夾" + dir + " 成功!");

            } else {
                log.info("建立資料夾" + dir + " 失敗!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return flag;
    }
}

6.在resource/WEB-INF建立spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.iot.ftp.config.FtpConfiguration

7.使用說明

7.1 包引入

<dependency>
            <groupId>com.iot</groupId>
            <artifactId>iot-ftp-spring-boot-starter</artifactId>
            <version>最新版本</version>
        </dependency>

7.2 配置檔案配置資訊

iot-ftp:
  isopen: true #是否開啟
  host: 192.168.2.1 
  port: 21
  username: user
  password: pwd
  encoding: UTF-8

7.3 使用說明

@Autowired
private IotFtpService iotFtpService;

@GetMapping("/ftp")
    public Object ftp() throws Exception{
        for (int i = 0; i < 10; i++) {
            boolean b = iotFtpService.uploadFile("ftp/testganinfo", "logstash-5.6.9.tar.gz", "f://logstash-5.6.9.tar.gz");
            System.out.println("上傳檔案==>"+b);
            boolean b1 = iotFtpService.downloadFile("ftp/testganinfo", "logstash-5.6.9.tar.gz", "c://");
            System.out.println("下載檔案==>"+b1);
        }
        return iotFtpService.retrieveFileNames("ftp/testganinfo");
    }

更多springboot相關知識關注部落格[持續更新]