java对象池apache common-pool2应用示例-FtpCilent对象池

背景

之前写过java调用ftpClient实现大文件传输的文章:ubuntu20-04配置ftp服务器-java通过ftpClient处理文件示例

文章中代码里简单的用了FTPUtil 类每次连接ftp的时候都要new一个新的ftpClient对象,连接登录,用完都要断开连接等操作,在实际应用中往往会遇到性能问题。所以可以利用apache提供的common-pool2里的对象池来做一个ftpClient的对象池。
池化技术主要涉及到下面几个东西:

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;

思路步骤

一般写一个对象池可以分为以下几步:
1.构建配置类
2.构建工厂类
3.构建对象池类
4.构建业务template类

构建FtpProperties配置类

配置类里主要配置一些ftp需用用到的参数,比如用户名、密码、ftp服务器地址、端口等等

import javax.annotation.PostConstruct;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment;

@ConfigurationProperties(FtpProperties.PREFIX)
public class FtpProperties {
    
    public static final String PREFIX = "ftp";

    @Autowired
    private Environment environment;
    
    @PostConstruct
    public void init() {
        if (StringUtils.isEmpty(path)) {
            this.setPath(environment.resolvePlaceholders("${spring.application.name:}"));
        }
        requireNotNull(this.domain);
        requireNotNull(this.server);
        requireNotNull(this.port);
        requireNotNull(this.path);
        requireNotNull(this.username);
        requireNotNull(this.password);
    }
    
    /**
     * 服务器域名
     */
    private String domain;
    
    /**
     * 服务器内部域名
     */
    private String innerDomain;
    
    /**
     * 服务器外部域名
     */
    private String outerDomain;
    
    /**
     * 服务器IP地址
     */
    private String server;
    
    /**
     * 服务器端口
     */
    
    private int port;
    
    /**
     * 服务器目录
     */
    private String path;
    
    /**
     * 登陆用户名
     */
    private String username;
    
    /**
     * 登陆密码
     */
    private String password;
    
    /**
     * 被动模式
     */
    private boolean passiveMode = false;
    
    /**
     * 连接池配置
     */
    private Pool pool = new Pool();

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    public String getInnerDomain() {
        return innerDomain;
    }

    public void setInnerDomain(String innerDomain) {
        this.innerDomain = innerDomain;
    }

    public String getOuterDomain() {
        return outerDomain;
    }

    public void setOuterDomain(String outerDomain) {
        this.outerDomain = outerDomain;
    }

    public String getServer() {
        return server;
    }

    public void setServer(String server) {
        this.server = server;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    
    public boolean isPassiveMode() {
        return passiveMode;
    }

    public void setPassiveMode(boolean passiveMode) {
        this.passiveMode = passiveMode;
    }

    public Pool getPool() {
        return pool;
    }

    public void setPool(Pool pool) {
        this.pool = pool;
    }

    public static class Pool {
        
        private int maxTotal = 100;
        
        private int minIdle = 2;
        
        private int maxIdle = 5;
        
        private int maxWaitMillis = 3000;

        public int getMaxTotal() {
            return maxTotal;
        }

        public void setMaxTotal(int maxTotal) {
            this.maxTotal = maxTotal;
        }

        public int getMinIdle() {
            return minIdle;
        }

        public void setMinIdle(int minIdle) {
            this.minIdle = minIdle;
        }

        public int getMaxIdle() {
            return maxIdle;
        }

        public void setMaxIdle(int maxIdle) {
            this.maxIdle = maxIdle;
        }

        public int getMaxWaitMillis() {
            return maxWaitMillis;
        }

        public void setMaxWaitMillis(int maxWaitMillis) {
            this.maxWaitMillis = maxWaitMillis;
        }
        
    }
    
    private void requireNotNull(Object obj) {
        if (obj == null) throw new NullPointerException();
    }

}

具体配置写入到nacos里的配置文件中:

ftp:
  domain: https://xxx
  innerDomain: https://xxx
  outerDomain: https://xxx
  server: 192.168.0.179
  port: 21
  username: ftpuser
  password: ftpuser
  pool:
    maxTotal: 100
    minIdle: 2
    maxIdle: 5
    maxWaitMillis: 3000

项目启动时通过@PostConstruct注解,可以直接运行上面的init方法来初始化nacos中的配置信息。

注:@PostConstruct 一般用在非静态void方法上,启动时加载,一般的加载顺序是这样:构造方法 ——> @Autowired —— > @PostConstruct ——> 静态方法 (按此顺序加载)

构建FtpClientFactory工厂类

实现org.apache.commons.pool2.PooledObjectFactory接口构建工厂,用于 生产,销毁,激活,验证 我们的池化资源对象。

import java.io.IOException;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;

import ly.mp.iov.ftp.config.FtpProperties;

/**
 * 
 * FTP客户端工厂类
 *
 */
public class FtpClientFactory implements PooledObjectFactory<FTPClient> {
    
    private static final String ROOT_DIR = "/";
    
    private static final int BUFFER_SIZE = 1024 * 1024;
    
    private FtpProperties ftpProperties;
    
    public FtpClientFactory(FtpProperties ftpProperties) {
        this.ftpProperties = ftpProperties;
    }

    @Override
    public PooledObject<FTPClient> makeObject() throws Exception {
        FTPClient ftpClient = new FTPClient();
        return new DefaultPooledObject<>(ftpClient);
    }

    @Override
    public void destroyObject(PooledObject<FTPClient> pooledObject) throws Exception {
        FTPClient ftpClient = pooledObject.getObject();
        try {
            ftpClient.logout();
            if (ftpClient.isConnected()) {
                ftpClient.disconnect();
            }
        } catch (IOException e) {
            throw new RuntimeException("Could not disconnect from server.", e);
        }
        
    }

    @Override
    public boolean validateObject(PooledObject<FTPClient> pooledObject) {
        FTPClient ftpClient = pooledObject.getObject();
        try {
            return ftpClient.sendNoOp();
        } catch (IOException e) {
            return false;
        }
    }

    @Override
    public void activateObject(PooledObject<FTPClient> pooledObject) throws Exception {
        FTPClient ftpClient = pooledObject.getObject();
        ftpClient.connect(ftpProperties.getServer(), ftpProperties.getPort());
        ftpClient.setControlEncoding("UTF-8");
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
        if (!ftpClient.login(ftpProperties.getUsername(), ftpProperties.getPassword())) {
            throw new RuntimeException("Could not login to server.");
        }
        if (ftpProperties.isPassiveMode()) {            
            ftpClient.enterLocalPassiveMode();
        }
        /**
         * 开启缓存,大幅提高传输速度
         */
        ftpClient.setBufferSize(BUFFER_SIZE);
    }

    @Override
    public void passivateObject(PooledObject<FTPClient> pooledObject) throws Exception {
        FTPClient ftpClient = pooledObject.getObject();
        try {
            ftpClient.changeWorkingDirectory(ROOT_DIR);
            ftpClient.logout();
            if (ftpClient.isConnected()) {
                ftpClient.disconnect();
            }
        } catch (IOException e) {
            throw new RuntimeException("Could not disconnect from server.", e);
        }
    }

    public FtpProperties getFtpProperties() {
        return ftpProperties;
    }

}

上面实现了PooledObjectFactory中的下面几个方法:
1.生产 makeObject 方法
用于对象的新建,一般是 new 出来之后包装一下。而什么时候需要新建呢,根据策略不同则时机不同。例如在没有闲置资源对象,且已存在的资源数不超过所设置的最大资源时新建。

2.销毁 destroyObject 方法:销毁一个对象,除了很容易想到的闲置过长时间被清理掉了导致需要销毁之外,还有如果进行了第三个方法且返回了 false ,那么也是需要销毁的。

3.验证 validateObject方法:检验这个对象是否还有有效,借出和归还时,以及内置后台线程检测闲置情况时,可以通过验证可以去除一些不符合业务逻辑的资源对象。默认这个方法是不被调用的,要开启则需要在PoolConfig中设置setTestOnBorrow , setTestOnReturn , setTestWhileIdle等属性。

4.激活 activeObject 方法: 在借用一个对象的时候调用,则可以在此重置其内部状态,那么返回的对象就像新的一样

  1. passivateObject 方法: 对应 activateObject 方法,是在归还一个对象的时候调用,注意不应与activateObject方法有业务逻辑上的冲突

这些是构建工厂类都会有的固式方法。

构建FtpClientPool对象池类

通过GenericObjectPool里的borrowObject可以借用一个对象,returnObject可以归还一个对象,这样ftpClient对象就由GenericObjectPool对象池管理了起来

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import ly.mp.iov.ftp.config.FtpProperties;

/**
 * 
 * FTP客户端连接池
 *
 */
public class FtpClientPool {
    
    private FtpClientFactory ftpClientFactory;
    
    private final GenericObjectPool<FTPClient> internalPool;
    
    public FtpClientPool(FtpClientFactory ftpClientFactory){
        this.ftpClientFactory = ftpClientFactory;
        FtpProperties properties = ftpClientFactory.getFtpProperties();
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxTotal(properties.getPool().getMaxTotal());
        poolConfig.setMinIdle(properties.getPool().getMinIdle());
        poolConfig.setMaxIdle(properties.getPool().getMaxIdle());
        poolConfig.setMaxWaitMillis(properties.getPool().getMaxWaitMillis());
        this.internalPool = new GenericObjectPool<FTPClient>(ftpClientFactory, poolConfig);
    }
    
    public FTPClient getFtpClient() {
        try {
            return internalPool.borrowObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public void returnFtpClient(FTPClient ftpClient) {
        internalPool.returnObject(ftpClient);
    }
    
    public void destroy() {
        internalPool.close();
    }

    public FtpClientFactory getFtpClientFactory() {
        return ftpClientFactory;
    }

}
构建FtpTemplate业务接口类

对象池构建好之后就可以封装业务接口了

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;

import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ly.mp.iov.ftp.config.FtpProperties;
import ly.mp.iov.ftp.exception.RemoteFileExistException;
import ly.mp.iov.ftp.util.FtpClientUtils;

public class FtpTemplate {
    
    private static final Logger logger = LoggerFactory.getLogger(FtpTemplate.class);
    
    private FtpProperties ftpProperties;
    
    private FtpClientPool ftpClientPool;
    
    public FtpTemplate(FtpClientPool ftpClientPool) {
        this.ftpClientPool = ftpClientPool;
        this.ftpProperties = ftpClientPool.getFtpClientFactory().getFtpProperties();
    }
    
    /**
     * 读取FTP文件
     * @param path
     * @param out
     * @return 读取成功
     */
    public boolean read(String path, OutputStream out) {
        FTPClient ftpClient = ftpClientPool.getFtpClient();
        try {
            if (!FtpClientUtils.exists(path, ftpClient)) {
                logger.info("【{}】文件不存在", path);
                return false;
            }
            return ftpClient.retrieveFile(path, out);
        } catch (IOException e) {
            throw new RuntimeException("文件读取失败");
        } finally {
            ftpClientPool.returnFtpClient(ftpClient);
        }
    }
    
    /**
     * 上传文件到FTP服务器
     * @param file 本地文件路径
     * @param fileName
     * @return
     */
    public FtpFile write(String file, String fileName) {
        try (InputStream in = new FileInputStream(file)){
            return write(in, fileName);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件不存在:" + file);
        } catch (IOException e) {
            throw new RuntimeException("文件关闭失败");
        }
    }
    
    /**
     * 上传文件到FTP服务器
     * @param in
     * @param fileName
     * @param filePath
     * @return
     */
    public FtpFile write(String file, String fileName, String filePath) {
        try (InputStream in = new FileInputStream(file)){
            return write(in, fileName, filePath);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件不存在:" + file);
        } catch (IOException e) {
            throw new RuntimeException("文件关闭失败");
        }
    }
    
    /**
     * 上传文件流到FTP服务器
     * @param in
     * @param fileName
     * @return
     */
    public FtpFile write(InputStream in, String fileName) {
        String filePath = ftpProperties.getPath() + "/"
                + DateFormatUtils.format(new Date(), "yyyyMM") + "/" + FtpClientUtils.UUID() + "/";
        return write(in, fileName, filePath);
    }
    
    /**
     * 上传文件流到FTP服务器
     * @param in 文件流
     * @param fileName 文件名
     * @param filePath 文件路径
     * @return
     */
    public FtpFile write(InputStream in, String fileName, String filePath) {
        if (!validateFilePath(filePath)) {
            throw new IllegalArgumentException("文件路径必须在当前服务路径下");
        }
        
        FTPClient ftpClient = ftpClientPool.getFtpClient();
        try {
            // 创建并移动到指定目录
            String path = FtpClientUtils.trimPath(filePath);
            try {
                FtpClientUtils.makeDirectory(path, ftpClient);
            } catch (IOException e) {
                throw new RuntimeException("FTP服务器创建目录失败", e);
            }
            try {
                FtpClientUtils.storeFile(in, fileName, ftpClient);
            } catch (RemoteFileExistException e) {
                throw new RemoteFileExistException(String.format("【%s】文件已存在", "/" + path + fileName));
            }
            FtpFile ftpFile = new FtpFile();
            ftpFile.setInnerPath(ftpProperties.getInnerDomain() + "/" + path + fileName);
            ftpFile.setOuterPath(ftpProperties.getOuterDomain() + "/" + path + fileName);
            return ftpFile;
        } finally {
            ftpClientPool.returnFtpClient(ftpClient);
        }
    }
    
    /**
     * 删除FTP服务器上指定文件,仅删除文件
     * @param filePath
     * @return
     */
    public boolean delete(String filePath) {
        return delete(filePath, FileFilter.FILE);
    }
    
    /**
     * 删除FTP服务器上指定文件,可以指定删除文件、文件夹还是所有
     * @param filePath
     * @return
     */
    public boolean delete(String filePath, FileFilter fileFilter) {
        if (!validateFilePath(filePath)) {
            throw new IllegalArgumentException("删除文件路径必须在当前服务路径下");
        }
        
        FTPClient ftpClient = ftpClientPool.getFtpClient();
        try {           
            return FtpClientUtils.deleteFile(filePath, ftpClient, fileFilter);
        } finally {
            ftpClientPool.returnFtpClient(ftpClient);
        }
    }
    
    /**
     * 判断文件是否存在
     * @param filePath 
     * @return
     */
    public boolean exists(String filePath) {
        FTPClient ftpClient = ftpClientPool.getFtpClient();
        try {
            return FtpClientUtils.exists(filePath, ftpClient);
        } finally {
            ftpClientPool.returnFtpClient(ftpClient);
        }
    }
    
    /**
     * 获取FTP客户端,使用必须调用{@code #getFtpClient()}方法
     * @return
     */
    public FTPClient getFtpClient() {
        return ftpClientPool.getFtpClient();
    }
    
    /**
     * 归还FTP客户端
     * @param ftpClient
     */
    public void returnFtpClient(FTPClient ftpClient) {
        ftpClientPool.returnFtpClient(ftpClient);
    }
    
    /**
     * 获取配置类
     * @return
     */
    public FtpProperties getFtpProperties() {
        return ftpProperties;
    }

    /**
     * 校验删除文件路径必须在本服务所属路径下
     * @param filePath
     * @return
     */
    private boolean validateFilePath(String filePath) {
        return filePath.startsWith("/" + ftpProperties.getPath())
                || filePath.startsWith(ftpProperties.getPath());
    }

}

这里面的业务方法可根据自身业务更改增减,本文的侧重点在于演示池化技术的应用来解决ftp的连接性能问题。

最后新建FtpAutoConfiguration把FtpProperties、FtpClientFactory、FtpClientPool、FtpTemplate都注入到spring中:

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 org.springframework.core.env.Environment;

import ly.mp.iov.ftp.core.FtpClientFactory;
import ly.mp.iov.ftp.core.FtpClientPool;
import ly.mp.iov.ftp.core.FtpTemplate;

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(FtpProperties.class)
@ConditionalOnProperty(name = "ftp.enabled", havingValue="true", matchIfMissing = true)
public class FtpAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public FtpProperties ftpProperties(Environment environment) {
        return new FtpProperties();
    }
    
    @Bean
    public FtpClientFactory ftpClientFactory(FtpProperties ftpProperties) {
        return new FtpClientFactory(ftpProperties);
    }
    
    @Bean
    public FtpClientPool ftpClientPool(FtpClientFactory ftpClientFactory) {
        return new FtpClientPool(ftpClientFactory);
    }
    
    @Bean
    public FtpTemplate ftpTemplate(FtpClientPool ftpClientPool) {
        return new FtpTemplate(ftpClientPool);
    }

}

其他服务用的时候就可以直接通过@Autowired来使用了:

@Autowired
private FtpTemplate ftpTemplate;

把剩余的用到的业务类也贴一下吧:

/**
 * 
 * 文件过滤枚举类
 *
 */
public enum FileFilter {
    
    ALL,
    
    FILE,
    
    DIRECTORY;

}
public class FtpFile {
    
    private String innerPath;
    
    private String outerPath;

    public String getInnerPath() {
        return innerPath;
    }

    public void setInnerPath(String innerPath) {
        this.innerPath = innerPath;
    }

    public String getOuterPath() {
        return outerPath;
    }

    public void setOuterPath(String outerPath) {
        this.outerPath = outerPath;
    }

    @Override
    public String toString() {
        return "FtpFile [innerPath=" + innerPath + ", outerPath=" + outerPath + "]";
    }

}
/**
 * 
 * FTP服务器文件已存在异常
 *
 */
public class RemoteFileExistException extends RuntimeException {
    
    private static final long serialVersionUID = 5266212078073813671L;

    public RemoteFileExistException() {
        super();
    }
    
    public RemoteFileExistException(String msg) {
        super(msg);
    }

}
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ly.mp.iov.ftp.core.FileFilter;
import ly.mp.iov.ftp.exception.RemoteFileExistException;

/**
 * 
 * FTP工具类
 */
public class FtpClientUtils {
    
    private static final Logger logger = LoggerFactory.getLogger(FtpClientUtils.class);
    
    /**
     * 通配符
     */
    private static final String FILE_WILDCARD = "*";
    
    /**
     * 文件路径分隔符
     */
    private static final String FILE_SEPARATOR = "/";
    
    /**
     * 生成UUID
     * @return
     */
    public static String UUID() {
        return java.util.UUID.randomUUID().toString().replaceAll("-", "");
    }
    
    /**
     * 创建目录并不返回根目录
     * @param filePath 文件路径
     * @param ftpClient
     * @return
     */
    public static boolean makeDirectory(String filePath, FTPClient ftpClient) throws IOException {
        boolean isOK = false;
        String directory = filePath.substring(0, filePath.lastIndexOf(FILE_SEPARATOR) + 1);
        if (!directory.equalsIgnoreCase(FILE_SEPARATOR) && !ftpClient.changeWorkingDirectory(directory)) {
            int start = 0;
            int end = 0;
            if (directory.startsWith(FILE_SEPARATOR)) {
                start = 1;
            } else {
                start = 0;
            }
            end = directory.indexOf(FILE_SEPARATOR, start);
            while (true) {
                String subDirectory = filePath.substring(start, end);
                if (!ftpClient.changeWorkingDirectory(subDirectory)) {
                    if (ftpClient.makeDirectory(subDirectory)) {
                        ftpClient.changeWorkingDirectory(subDirectory);
                    } else {
                        throw new IOException(String.format("创建[%]->[%]目录失败", filePath, subDirectory));
                    }
                }
                start = end + 1;
                end = directory.indexOf(FILE_SEPARATOR, start);
                if (end <= start) {
                    isOK = true;
                    break;
                }
            }
        } else {
            isOK = true;
        }
        return isOK;
    }
    
    /**
     * 上传文件到指定服务器目录下
     * @param in 文件流
     * @param fileName 文件名
     * @param ftpClient FTP客户端
     * @throws RemoteFileExistException FTP服务器文件已存在
     * @return
     */
    public static void storeFile(InputStream in, String fileName, FTPClient ftpClient) {
        // 使用BufferedInputStream大幅提升上传性能
        try (BufferedInputStream bufferedIn = new BufferedInputStream(in)) {
            // FTP服务器文件名使用ISO-8859-1编码,解决中文乱码问题
            String remoteFileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
            boolean success = ftpClient.storeFile(remoteFileName, bufferedIn);
            if (!success) {
                throw new RuntimeException("文件上传失败");
            }
        } catch (IOException e) {
            throw new RuntimeException("文件上传失败", e);
        }
    }
    
    /**
     * 递归删除文件和文件夹
     * @param filePath
     * @param ftpClient
     * @param fileFilter
     * @return
     */
    public static boolean deleteFile(String filePath, FTPClient ftpClient, FileFilter fileFilter) {
        FilePath realFilePath = deconstructionFilePath(filePath);
        
        try {
            if (StringUtils.isNotEmpty(realFilePath.getFileDir())) {
                ftpClient.changeWorkingDirectory(realFilePath.getFileDir());
            }
            FTPFile[] files = ftpClient.listFiles();
            int i = 0;
            for (; i < files.length; i++) {
                // 跳过当前目录和上级目录
                if (".".equals(files[i].getName()) || "..".equals(files[i].getName())) {
                    continue;
                }
                if (!FILE_WILDCARD.equals(realFilePath.getFileName()) && !files[i].getName().equals(realFilePath.getFileName())) {
                    continue;
                }
                if (files[i].isDirectory() && !FileFilter.FILE.equals(fileFilter)) {
                    FTPFile[] subFiles = ftpClient.listFiles(files[i].getName());
                    if (subFiles.length > 2) {
                        ftpClient.changeWorkingDirectory(files[i].getName());
                        // 递归删除文件夹下的文件
                        if (!deleteFile(FILE_WILDCARD, ftpClient, FileFilter.ALL)) {
                            return false;
                        }
                        ftpClient.changeToParentDirectory();
                    }
                    return ftpClient.removeDirectory(utf8ToIso(realFilePath.getFileName()));
                } else if (files[i].isFile() && !FileFilter.DIRECTORY.equals(fileFilter)) {
                    return ftpClient.deleteFile(utf8ToIso(files[i].getName()));
                }
            }
            if (i == files.length) {
                logger.error("文件删除失败:【{}】文件不存在,文件过滤器【{}】", filePath, fileFilter.name());
                return false;
            } else {
                return true;
            }
        } catch (IOException e) {
            logger.error("文件删除失败:【{}】文件不存在", filePath);
            return false;
        }
    }
    
    /**
     * 将文件路径字符串解构为目录和文件名
     * @param filePathStr
     * @return
     */
    private static FilePath deconstructionFilePath(String filePathStr) {
        FilePath filePath = new FilePath();
        if (filePathStr.endsWith(FILE_SEPARATOR)) {
            filePathStr = filePathStr.substring(0, filePathStr.length()-1);
        }
        if (filePathStr.contains(FILE_SEPARATOR)) {
            filePath.setFileDir(filePathStr.substring(0, filePathStr.lastIndexOf('/')));
            filePath.setFileName(filePathStr.substring(filePath.getFileDir().length()+1));
        } else {
            filePath.setFileDir("");
            filePath.setFileName(filePathStr);
        }
        return filePath;
    }
    
    /**
     * 判断FTP服务器文件是否存在
     * @param filePath
     * @param ftpClient
     * @return
     */
    public static boolean exists(String filePath, FTPClient ftpClient) {
        FilePath realFilePath = deconstructionFilePath(filePath);
        if (StringUtils.isNotEmpty(realFilePath.getFileDir())) {
            try {
                ftpClient.changeWorkingDirectory(realFilePath.getFileDir());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        // 先判断FTP服务器上是否已存在的文件
        FTPFile[] ftpFiles;
        try {
            ftpFiles = ftpClient.listFiles();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        for (int i = 0; i < ftpFiles.length; i++) {
            if (ftpFiles[i].getName().equals(realFilePath.getFileName())) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * UTF-8转ISO-8859-1
     * @param str
     * @return
     */
    public static String utf8ToIso(String str) {
        try {
            // FTP服务器文件名使用ISO-8859-1编码,解决中文乱码问题
            return new String(str.getBytes("UTF-8"), "ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * ISO-8859-1转UTF-8
     * @param str
     * @return
     */
    public static String isoToUtf8(String str) {
        try {
            // FTP服务器文件名使用ISO-8859-1编码,解决中文乱码问题
            return new String(str.getBytes("ISO-8859-1"), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 规范格式文件路径格式
     * @param filePath
     * @return
     */
    public static String trimPath(String filePath) {
        StringBuilder filePathBuilder = new StringBuilder(filePath);
        if (filePathBuilder.length() > 1 && '/' == filePathBuilder.charAt(0)) {
            filePathBuilder.deleteCharAt(0);
        }
        if (filePathBuilder.length() > 0 && '/' != filePathBuilder.charAt(filePathBuilder.length()-1)) {
            filePathBuilder.append('/');
        }
        return filePathBuilder.toString();
    }
    
    static class FilePath {
        
        private String fileDir;
        
        private String fileName;

        public String getFileDir() {
            return fileDir;
        }

        public void setFileDir(String fileDir) {
            this.fileDir = fileDir;
        }

        public String getFileName() {
            return fileName;
        }

        public void setFileName(String fileName) {
            this.fileName = fileName;
        }
        
    }

}

参考:https://blog.csdn.net/qq_37186947/article/details/104227552

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容