java实现从FTP服务器上获取图片,并转化为base64编码

1、博客描述

该篇博客主要写的是自己用java从ftp服务器上获取图片,并把图片流转化为base64编码的过程中,所遇到的一些问题。

2、需求描述

需要从18台FTP服务器上获取图片数据,并进行封装,大概是每台FTP的根目录下有9个文件夹,每个文件夹下面有32张图片,数据量是 18932=5184张图片

3、代码

实现:首先考虑的是用一个定时线程,轮询遍历18台ftp服务器,获取图片,但是考虑到该方法只适合数据量较小的情况,所以最后决定使用18个定时线程,去获取图片。每遍历一次就创建一个定时线程,每个线程单独工作。

下面是FtpUtil工具类,获取图片流,并转化为base64编码

import com.alibaba.fastjson.JSONObject;
import com.uniview.entity.FtpInfoEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@Slf4j
@Service
public class FTPUtil {

    /**
     * 登陆ftp服务器
     */
    public FTPClient loginFtp(FtpInfoEntity ftpInfoEntity){
        FTPClient ftpClient = new FTPClient();
        //设置超时时间
        ftpClient.setDefaultTimeout(90 * 1000);
        ftpClient.setConnectTimeout(90 * 1000);
        ftpClient.setDataTimeout(90 * 1000);

        try {
            ftpClient.connect(ftpInfoEntity.getFtpIp(), ftpInfoEntity.getFtpPort());
            ftpClient.login(ftpInfoEntity.getFtpUserName(), ftpInfoEntity.getFtpPassword());

            //是否成功登陆服务器
            int reply = ftpClient.getReplyCode();
            if (!FTPReply.isPositiveCompletion(reply)){
                log.error("{}登陆FTP服务器失败...", ConstantUtil.ERROR_LOG);
                ftpClient.disconnect();
            }
        }catch (Exception e){
            log.error("{}登陆FTP服务器失败:{}", ConstantUtil.ERROR_LOG, e);
        }
        return ftpClient;
    }

    /**
     * 下载图片
     * @param ftpClient
     * @param path
     * @return
     */
    public Map<String, JSONObject> downloadPic(FTPClient ftpClient, String path){
        Map<String, JSONObject> picInfoMap = new HashMap<>();

        try{
            if(null != ftpClient && !StringUtils.isBlank(path)){

                String relPath = path;
                String base64 = "";
                boolean changeWorkDir = ftpClient.changeWorkingDirectory(path);
                if (changeWorkDir){
                    //切换目录成功
                    //设置被动模式,防止在Linux上,由于安全限制,可能某些端口没有开启,出现阻塞
                    ftpClient.enterLocalPassiveMode();
					//读取图片需要添加该方法
                    ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                    //遍历下载的目录
                    FTPFile[] ftpFiles = ftpClient.listFiles();
                    if (null == ftpFiles || 0 == ftpFiles.length){
                        log.info("{}该目录{}下为空...", ConstantUtil.INFO_LOG, path);
                        return picInfoMap;
                    }

                    for (FTPFile ftpFile : ftpFiles){
                        //判断该文件是否为文件夹
                        if (ftpFile.isDirectory()){
                            log.info("{}{}该文件是文件夹...", ConstantUtil.INFO_LOG, ftpFile.getName());
                            relPath = path + ftpFile.getName() + "/";
                            log.info("relPath是:{}", relPath);
                            Map<String, JSONObject> childMap = downloadPic(ftpClient, relPath);
                            for (String key : childMap.keySet()){
                                picInfoMap.put(key, childMap.get(key));
                            }
                        }else if (ftpFile.isFile()) {
                            //解决中文乱码问题,两次解码
                            byte[] bytes = ftpFile.getName().getBytes("UTF-8");
                            String fileName = new String(bytes, "iso-8859-1");
                            //获取时间到 20200709200509
                            String lastModifyTimeStr = ftpClient.getModificationTime(fileName);

                            InputStream retrieveFileStream = null;
                            ByteArrayOutputStream outputStream = null;
                            try {
                                //获取文件流
                                retrieveFileStream = ftpClient.retrieveFileStream(fileName);
                                if (null != retrieveFileStream) {
                                    byte[] data = null;
                                    outputStream = new ByteArrayOutputStream();
                                    //TimeUnit.MILLISECONDS.sleep(300);
                                    data = new byte[1024];
                                    int len = 0;
                                    while (-1 != (len = retrieveFileStream.read(data))) {
                                        outputStream.write(data, 0, len);
                                    }

                                    if (null != outputStream){
                                        data = outputStream.toByteArray();
                                        Base64.Encoder encoder = Base64.getEncoder();
                                        base64 = encoder.encodeToString(data);
                                    }else {
                                        log.error("{}输出流为空,不能转化为base64位编码...", ConstantUtil.ERROR_LOG);
                                    }


                                    JSONObject dataJson = new JSONObject();
                                    dataJson.put("base64", base64);
                                    dataJson.put("time", lastModifyTimeStr);

                                    picInfoMap.put(fileName, dataJson);
                                    //删除该文件
                                    //文件路径
                                    boolean b = ftpClient.deleteFile(fileName);
                                    if (!b) {
                                        log.error("{}删除图片{}异常...", ConstantUtil.ERROR_LOG, fileName);
                                    } else {
                                        log.info("{}删除文件成功:{}", ConstantUtil.INFO_LOG, fileName);
                                    }

                                }else {
                                    log.error("{}数据流为空...", ConstantUtil.ERROR_LOG);
                                }

                            }catch (Exception e){
                                log.error("{}读取图片异常:{}", ConstantUtil.ERROR_LOG, e);
                            }finally {
                                if (null != outputStream){
                                    outputStream.flush();
                                    outputStream.close();
                                }
                                if (null != retrieveFileStream){
                                    retrieveFileStream.close();
                                    ftpClient.completePendingCommand();
                                }
                            }
                        }


                    }
                }
            }
        }catch (IOException e) {
            log.error("{}FTP操作文件异常:{}", ConstantUtil.ERROR_LOG, e);
        }

        return picInfoMap;
    }

    /**
     * 退出登陆和连接
     * @param ftpClient
     */
    public void logout(FTPClient ftpClient){
        if (ftpClient.isConnected()){
            try {
                ftpClient.logout();
                ftpClient.disconnect();
            } catch (IOException e) {
                log.error("{}关闭ftp服务器连接异常:{}", ConstantUtil.ERROR_LOG, e);
            }
        }
    }
}

@Data
@ToString
public class FtpInfoEntity implements Serializable {
    /**
     * 编号
     */
    private String id;

    /**
     * FTP的IP
     */
    private String ftpIp;

    /**
     * FTP的端口
     */
    private int ftpPort;

    /**
     * FTP的用户名
     */
    private String ftpUserName;

    /**
     * FTP的密码
     */
    private String ftpPassword;

    /**
     * 文件所在的位置
     */
    private String path;

}

4、遇到的问题及解决方法

在实现中,遇到了好多问题,下面 一 一 列举。

4.1 使用ftpClient.retrieveFileStream()方法的坑

开始编码时,使用了该方法,但是没有调用 ftpClient.completePendingCommand(); 该方法,导致程序读取一张图片后就失败了,通过百度发现,使用 ftpClient.retrieveFileStream() 方法批量处理文件时,需要在其流关闭后,调用 ftpClient.completePendingCommand(); 该方法。最后添加上去了,程序运行成功。

在每次执行完下载操作之后,completePendingCommand()会一直在等FTP Server返回226 Transfer complete,但是FTP Server只有在接受到InputStream 执行close方法时,才会返回。所以一定先要执行close方法。不然在第一次下载一个文件成功之后,之后再次获取inputStream 就会返回null。
来源:JAVA实现FTP多文件下载,说一说ftpClient.retrieveFileStream方法遇到的坑

4.2 ftpClient.enterLocalPassiveMode();的问题

开始编码测试的时候,我没有使用线程,写了一个测试类,进行测试,能很快的获取到一台ftp服务器上的所有数据,没有任何异常产生。但是当我把测试类的方法运用到项目中的时候,用单线程去执行,发现读了几张图片后,线程就不执行工作了,也没有异常抛出;任何转化为debugbug模式调试,一步一步的调试,发现程序又是正常的,没有任何问题,也没有卡死。这是什么原因呢?百度了好多和请教了同事,最后觉得有问题的地方是读取文件太慢了,程序执行过快,导致了某些异常。最后在获取流后,线程休眠了0.3秒,任何再执行,没有任何问题,执行成功。

重点:我把 ftpClient.enterLocalPassiveMode(); 该方法开始的时候写在了ftpClient.login()的前面

当我连接18台ftp服务器(模拟的18台ftp服务器)进行测试时,18个定时线程都启动成功,并且都在处理数据,但是有几个定时线程在处理的过程中“卡死”了,没有捕获到异常,很难定位。。。。。。。 同时该定时线程之后就不再工作了,一度怀疑是自己的设计方案有问题,但是18个线程,只有几个线程有问题,其它十几个线程都没有问题,这就没有怀疑自己的设计方案了。同时觉得有可能是 服务器性能的问题,或者是我本机性能的问题,不足以支撑这么多线程(35个线程以上),但是没有18台FTP服务器进行测试,减少了线程数量,还是出现了问题同样的问题,思考中。。。。。请教中。。。。。。。

最后怀疑是不是我写的下载方法有问题呢?然后百度、查看同事之前写的代码,发现 ftpClient.enterLocalPassiveMode(); 该方法都写在了ftpClient.login()的后面
最后尝试着把该方法换了个位置,谢天谢地,终于好了。。。。。。。。。。。。。
我不知道这个的原因是啥,反正是成功的捕获到异常了。。。。。。。

当执行18个定时线程时,某个线程出现问题了,能捕获到异常了,线程虽然 在某一步 “死” 了,但是处理异常后,不妨碍下一次的定时操作。。。。。。。。。

4.3 捕获的异常

捕获的异常主要有三个:

  1. 切换目录异常
    在这里插入图片描述

  2. 读取文件异常
    在这里插入图片描述

  3. 获取文件时间异常
    在这里插入图片描述

捕获的异常:

java.net.SocketException: Connection reset by peer: socket write error

java.net.SocketException: Connection reset

org.apache.commons.net.ftp.FTPConnectionClosedException: Connection closed without indication.

以上异常还没有解决,预估是性能问题,因为我是在两台 ftp服务器的根目录下各建了九个文件夹,一共18个文件夹,模拟18台ftp服务器。如有知道该异常的朋友,请告知,谢谢!

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值