docker部署ftp,java连接踩坑记录

下载镜像

docker pull fauria/vsftpd

启动容器

docker run -d --network=host -v /app/deploy/ftp:/home/vsftpd -e FTP_USER=ftp -e FTP_PASS=123456  --name vsftpd --restart=always fauria/vsftpd

注意此处不需要端口映射,之前我只映射了,20,21,22等端口发现,可以连接上ftp但是无法打开目录上传下载文件

使用java连接ftp

<dependency>
                <groupId>commons-net</groupId>
                <artifactId>commons-net</artifactId>
                <version>3.6</version>
            </dependency>

private static final Logger logger = LoggerFactory.getLogger(FtpServiceImpl.class);
    // ip地址
    @Value("${ftp.client.host}")
    private String host;

    // 端口号
    @Value("${ftp.client.port}")
    private Integer port;

    // 用户名
    @Value("${ftp.client.username}")
    private String username;

    // 密码
    @Value("${ftp.client.password}")
    private String password;

    // session链接超时时间
    @Value("${ftp.client.sessionConnectTimeout:15000}")
    private Integer sessionConnectTimeout;

    // channel链接超时时间
    @Value("${ftp.client.channelConnectedTimeout:15000}")
    private Integer channelConnectedTimeout;


    /**
     * 检查SFTP目录或文件是否存在
     *
     * @param remotePath
     * @return
     */
    @Override
    public boolean checkFileExist(String remotePath) {
        FTPClient ftpClient = loginFtp();

        try {
            ftpClient.changeWorkingDirectory(remotePath);
            // 设置为被动模型 行内的ftp需要在这里再设置一下
            ftpClient.enterLocalPassiveMode();
            String[] listNames = ftpClient.listNames(remotePath);
            if(listNames == null){
                return false;
            }
            if(listNames.length > 0){
                return true;
            }else{
                return false;
            }
        } catch (Exception e) {
            logger.warn("检查FTP目录或文件是否存在错误: ", e);
            return false;
        } finally {
            closeFtpClient(ftpClient);
        }
    }
    /**
     * 写远程文件
     *
     * @param data
     * @param remoteDirPath
     * @return
     */
    @Override
    public String writeFile(String data, String fileName, String remoteDirPath) {
        return writeFile(data.getBytes(StandardCharsets.UTF_8), fileName, remoteDirPath);
    }
    /**
     * 写远程文件
     *
     * @param data
     * @param remoteDirPath
     * @return
     */
    public String writeFile(byte[] data, String fileName, String remoteDirPath) {
        FTPClient ftpClient = loginFtp();
        try {
            boolean dirs = createDirs(remoteDirPath, ftpClient);
            if (!dirs) {
                logger.error("Remote path error. path:{}", remoteDirPath);
                return null;
            }

            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            ftpClient.enterLocalPassiveMode();
            try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data)) {
                String remoteFilePath = remoteDirPath + "/" + fileName;
                logger.info("upload file:::{}", remoteFilePath);

                ftpClient.storeFile(remoteFilePath,inputStream);
                ftpClient.logout();
                return remoteFilePath;
            }
        } catch (IOException ex) {
            logger.error("写远程文件错误", ex);
        } finally {
            closeFtpClient(ftpClient);
        }
        return null;
    }

    /**
     * 登录FTP
     * 
     * @return
     */
    private FTPClient loginFtp() {
        FTPClient ftpClient = new FTPClient();
        ftpClient.setControlEncoding("UTF-8");
        try {
            //设置连接超时时间
            ftpClient.setDataTimeout(sessionConnectTimeout);
            ftpClient.setConnectTimeout(channelConnectedTimeout);
            logger.info("连接FTP服务器中:" + host + ":" + port);
            // 设置为被动模型
            ftpClient.enterLocalPassiveMode();
            //连接ftp服务器
            ftpClient.connect(host, port);
            //登录ftp服务器
            ftpClient.login(username, password);
            // 是否成功登录服务器
            int replyCode = ftpClient.getReplyCode();
            if (FTPReply.isPositiveCompletion(replyCode)) {
                logger.info("连接FTP服务器成功:" + host + ":" + port);
            } else {
                logger.error("连接FTP服务器失败:" + host + ":" + port);
                closeFtpClient(ftpClient);
            }
        } catch (IOException e) {
            logger.error("连接ftp服务器异常", e);
            throw new RuntimeException(e);
        }
        return ftpClient;
    }

    /**
     * 关闭FTP连接
     *
     * @param ftpClient
     */
    public void closeFtpClient(FTPClient ftpClient) {
        if (ftpClient.isConnected()) {
            try {
                ftpClient.disconnect();
            } catch (IOException e) {
                logger.error("关闭FTP连接异常", e);
                throw new RuntimeException(e);
            }
        }
    }
    /**
     * 创建FTP目录,如果目录不存在就创建
     *
     * @param dirPath
     * @param ftpClient
     * @return
     */
    private boolean createDirs(String dirPath, FTPClient ftpClient) throws IOException {
        if (StringUtils.isEmpty(dirPath)) {
            return false;
        }

        if (!ftpClient.changeWorkingDirectory(dirPath)) {
            boolean flag = this.changeAndMakeWorkingDir(ftpClient, dirPath);
            if (!flag) {
                logger.error("路径切换(创建目录)失败");
                return false;
            }
        }
        return true;

    }
    /**
     * 路径切换(没有则创建)
     *
     * @param ftpClient FTP服务器
     * @param path      路径
     */
    public boolean changeAndMakeWorkingDir(FTPClient ftpClient, String path) {
        boolean flag = false;
        try {
            String[] path_array = path.split("/");
            for (String s : path_array) {
                boolean b = ftpClient.changeWorkingDirectory(s);
                if (!b) {
                    ftpClient.makeDirectory(s);
                    ftpClient.changeWorkingDirectory(s);
                }
            }
            flag = true;
        } catch (IOException e) {
            logger.error("路径切换异常", e);
        }
        return flag;
    }

容易踩坑点:

被动连接模式:ftpClient.enterLocalPassiveMode();

被动模式是ftp打开端口客户端连接该端口来传输数据,当时看了网上大部分的写法是将改行写在

//连接ftp服务器
ftpClient.connect(host, port);
//登录ftp服务器
ftpClient.login(username, password);

这两行后面或中间,但是会发生报错。需要将连接模式设置到这两行代码前面(这是我docker的ftp是这样。但是行内的ftp,需要将该连接模式设置到login后面才生效估计是和ftp服务器也有一点关系吧)


主动连接模式 :ftpClient.enterLocalActiveMode();

主动连接模式是客户端开发端口提供给ftp服务器来连,如果你本地防火墙打开了。那么你会发现你能连接ftp,但是下载或上传文件后文件是空的。java默认是主动模式