java Spring boot项目中FTP连接池及中文乱码和断点续传

      FTP是常用的文件存储方式,本文简单描述了在sping boot项目中使用连接池管理FTP客户端,并实现上传及下载时的断点续传的功能。

 1. maven依赖:

<dependency>
   <groupId>commons-net</groupId>
   <artifactId>commons-net</artifactId>
   <version>3.9.0</version>
</dependency>
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-pool2</artifactId>
   <version>2.11.1</version>
</dependency>
<dependency>
   <groupId>commons-io</groupId>
   <artifactId>commons-io</artifactId>
   <version>2.11.0</version>
</dependency>

2. 自定义FTP客户端对象工厂类,继承org.apache.commons.pool2.BasePooledObjectFactory,重写create,wrap,destoryObject和validateObject方法。

2.1 创建FTPClient

public FTPSClient create() throws IOException {
        FTPSClient client = new FTPSClient("TLS", true);
        client.setConnectTimeout(this.ftpProperties.getClientTimeOut());
        client.connect(this.ftpProperties.getHost(), this.ftpProperties.getPort());
        boolean result = client.login(this.ftpProperties.getUsername(), this.ftpProperties.getPassword());
        if (!result) {
            log.error("FTPServer refused connection,username is  {}", this.ftpProperties.getUsername());
            return null;
        } else {
            int replyCode = client.getReplyCode();
            if (!FTPReply.isPositiveCompletion(replyCode)) {
                client.disconnect();
                log.error("FTPServer refused connection");
                return null;
            } else {
                client.setFileTransferMode(this.ftpProperties.getTransferFileType());
                client.setControlEncoding(this.ftpProperties.getEncoding());
                // 被动模式
                if (this.ftpProperties.isPassiveMode()) {
                    client.enterLocalPassiveMode();
                } else {
                    client.enterLocalActiveMode();
                }

                client.execPBSZ(0L);
                client.execPROT("P");
                // 对于压缩包、可执行文件等若不采用二进制,可能会损坏
                client.setFileType(FTP.BINARY_FILT_TYPE);
                // 缓存大小
                client.setBufferSize(this.ftpProperties.getBufferSize() * 1024 * 1024);
                client.setControlEncoding(StandardCharsets.UTF_8.name());
                // ... 其他配置,如setDataTimeout(),setReceiveBufferSize()等等
                FTPClientConfig conf = new FTPClientConfig("UNIX");
                client.configure(conf);
                return client;
            }
        }
    }

2.2 其中验证可用性可以使用isAvailable和sendNoOp方法:

    @Override
    public boolean validateObject(PooledObject<FTPSClient> pooledObject) {
        FTPSClient client = (FTPSClient)pooledObject.getObject();
        if (client == null) {
            return false;
        }
        boolean available = client.isAvailable();
        if (!available) {
            return false;
        }
        try {
            return client.sendNoOp();
        } catch (IOException var5) {
            // 可打debug日志输出异常信息
            return false;
        }
    }

3. 自定义连接池管理类,可使用org.apache.commons.pool2.impl.GenericObjectPool

@Component
public class FTPClientPoolManager{
    private GenericObjectPool<FTPSClient> sftpPool;

    @PostConstruct
    public void initPool{
       GenericObjectPoolConfig<FTPSClient> poolConfig = new GenericObjectPoolConfig();
       poolConfig.setTestOnBorrow(true);
       // ...  其他连接池配置
       this.sftpPool = new GenericObjectPool(this.factory, poolConfig);
    }

    public FTPSClient borrowObject() throws Exception {
       return (FTPSClient)this.sftpPool.borrowObject();
    }

    public void returnObject(SSLSessionReuseFTPSClient client) {
        if (client != null) {
            try {
                // 恢复client当前工作目录到根目录
                client.changeWorkingDirectory("/");
                this.sftpPool.returnObject(client);
            } catch (Exception e) {
                // 日志信息
            }
        }
    }

4. 定义FTP服务类,实现上传下载等功能。

4.1 中文路径乱码的处理。FTP服务端默认字符集ISO_8859_1,对路径统一转码即可。如果切换工作目录时仍出现乱码,可以尝试一层一层循环切换进入。

    public static String convert2FtpCharset(String str) {
        return str == null ? null : new String(str.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
    }
// 切换工作目录。目录不存在则创建
public static void changeWorkingDirectory(FTPSClient client, String ftpFilePath) {
        try {
            String ftpFileName = ftpFilePath.replaceAll("\\\\", "/");
            String directory = ftpFileName.substring(0, ftpFileName.lastIndexOf("/"));
            String d = convert2FtpCharset(directory);
            String[] arr = d.split("/");
            String[] var6 = arr;
            int var7 = arr.length;

            for(int var8 = 0; var8 < var7; ++var8) {
                String s = var6[var8];
                if (!StringUtils.isBlank(s)) {
                    boolean aBoolean = client.changeWorkingDirectory(s);
                    if (!"".equals(s) && !aBoolean) {
                        boolean bBoolean = client.makeDirectory(s);
                        if (bBoolean) {
                            client.changeWorkingDirectory(s);
                        }
                    }
                }
            }
        } catch (Exception var12) {
            // 异常日志
        }
    }

4.2 文件上传到FTP服务器。读取本地文件流,从连接池获取client,创建FTP目录并切换,保存文件,执行后返还回连接池。

public boolean upload(String localFilePath, String ftpFilePath){
    FTPClient client;
    try{
       File localFile = new File(localFilePath);
       if ( !localFile.exists()){
           return false;
       }
       try (InputStream ins = Files.newInputStream(localFile.toPath()){
            // 创建FTP目录并切换为当前工作目录
            changeWorkingDirectory(client, ftpFilePath);
            // 文件名
            String fileName = ftpFilePath.substring(ftpFileName.lastIndexOf("/") + 1);
            // 上传
            boolean success = client.storeFile(convert2FtpCharset(fileName), ins);
       } 
    } finally {
        returnClient(pool, client);
    }

4.3 下载。使用client.retrieveFile 或者 retrieveFileStream进行下载。如果本地文件已经且小于FTP上的文件client.size(),设置client.restartOffset(localFileSize)进行续传,本地文件输出流可追加写入 new FileOutputStream(file, true)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值