Java+FTP+nginx+Openvpn实现内网外数据直传

        项目场景ABC三台电脑,AB同一局域网下,A内网、B可访问外网、C为公司电脑,需要将A电脑内网数据传输至公司。不知道有没有伙伴遇到这种场景,思路是A电脑的服务通过B电脑的nginx代理转发至C电脑,BC电脑网络互通问题采用Openvpn虚拟网段。思路是可行后边就开始一步步验证,就开始踩坑了。。。

        一、搭建FTP服务器

百度基本都是直接采用Windows自带的iis搭建FTP服务器,也可使用第三方程序搭建FTP例如FileZilla Server更方便操作。

附上链接Windows10 通过 IIS 搭建 FTP 服务器_iis 10 ftp服务器-CSDN博客  建议IP不要固定

 直接默认

二、编写业务代码(自行编辑,附上工具类)

package com.sclead.urdis.common.config;

import com.sclead.urdis.common.utils.date.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.tools.ant.taskdefs.Sleep;

import java.io.*;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Date;

@Slf4j
public class FtpUtil {

    /**
     * 获取FTPClient对象
     *
     * @param ftpHost     FTP主机服务器
     * @param ftpPassword FTP 登录密码
     * @param ftpUserName FTP登录用户名
     * @param ftpPort     FTP端口 默认为21
     * @return
     */
    public static FTPClient getFTPClient(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort) {

        FTPClient ftpClient = null;

        try {
            //创建一个ftp客户端
            ftpClient = new FTPClient();
            // 连接FTP服务器
            ftpClient.connect(ftpHost, ftpPort);
            // 登陆FTP服务器
            boolean login = ftpClient.login(ftpUserName, ftpPassword);
            log.info("登录FTP服务器:"+login);

            if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
                log.info("未连接到FTP,用户名或密码错误。");
                System.out.println("未连接到FTP,用户名或密码错误。");
                ftpClient.disconnect();
            } else {
                log.info("FTP连接成功。");
                System.out.println("FTP连接成功。");
            }
        } catch (SocketException e) {
            e.printStackTrace();
            log.info("FTP的IP地址可能错误,请正确配置。");
            System.out.println("FTP的IP地址可能错误,请正确配置。");
        } catch (IOException e) {
            e.printStackTrace();
            log.info("FTP的端口错误,请正确配置。");
            System.out.println("FTP的端口错误,请正确配置。");
        }
        return ftpClient;
    }


    /**
     * 下载文件
     *
     * @param ftpHost     ftp服务器地址
     * @param ftpUserName anonymous匿名用户登录,不需要密码。administrator指定用户登录
     * @param ftpPassword 指定用户密码
     * @param ftpPort     ftp服务员器端口号
     * @param ftpPath     ftp文件存放物理路径
     * @param fileName    文件路径
     */
    public static void downloadFile(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort, String ftpPath, String localPath, String fileName) {

        FTPClient ftpClient = null;

        try {
            ftpClient = getFTPClient(ftpHost, ftpUserName, ftpPassword, ftpPort);
            ftpClient.setControlEncoding("UTF-8"); // 中文支持
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            ftpClient.enterLocalPassiveMode();
            ftpClient.changeWorkingDirectory(ftpPath);

            File localFile = new File(localPath + File.separatorChar + fileName);
            OutputStream os = new FileOutputStream(localFile);
            ftpClient.retrieveFile(fileName, os);
            os.close();
            ftpClient.logout();

        } catch (FileNotFoundException e) {
            log.error("没有找到" + ftpPath + "文件");
            e.printStackTrace();
        } catch (SocketException e) {
            log.error("连接FTP失败.");
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
            log.error("文件读取错误。");
            e.printStackTrace();
        }
    }

    /**
     * 上传文件
     *
     * @param ftpHost     ftp服务器地址
     * @param ftpUserName anonymous匿名用户登录,不需要密码。administrator指定用户登录
     * @param ftpPassword 指定用户密码
     * @param ftpPort     ftp服务员器端口号
     * @param ftpPath     ftp文件存放物理路径
     * @param fileName    文件路径
     * @param input       文件输入流,即从本地服务器读取文件的IO输入流
     */
    public static void uploadFile(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort, String ftpPath, String fileName, InputStream input) {
        FTPClient ftp = null;
        try {
            ftp = getFTPClient(ftpHost, ftpUserName, ftpPassword, ftpPort);
            //ftp.setRemoteVerificationEnabled(false);  // 关闭远程IP校验
            ftp.setUseEPSVwithIPv4(true);
            // 检查目标目录是否存在,不存在则逐层创建
            String[] folders = ftpPath.split("/");   //循环创建子目录
            for (String folder : folders) {
                // 跳过空目录
                if (folder.isEmpty()) {
                    continue;
                }
                folder = new String(folder.getBytes("GBK"), FTP.DEFAULT_CONTROL_ENCODING);  //指定FTP中文格式
                // 尝试切换到当前目录,如果失败则创建目录
                if (!ftp.changeWorkingDirectory(folder)) {
                    ftp.makeDirectory(folder);
                    if (!ftp.changeWorkingDirectory(folder)) {
                        throw new IOException("无法切换到目录:" + folder);
                    }
                }
            }
            ftp.enterLocalPassiveMode();
            ftp.setFileType(FTP.BINARY_FILE_TYPE);
            String fullPath = ftpPath + "/" + fileName; // 完整的文件路径
            log.info("当前存入FTP路径为:"+fullPath);
            fullPath = new String(fullPath.getBytes("GBK"), FTP.DEFAULT_CONTROL_ENCODING); // 确保路径编码正确
            boolean b = ftp.storeFile(fullPath, input);
            if(b){
                System.out.println("上传文件成功");
            }
            //fileName = new String(fileName.getBytes("GBK"), FTP.DEFAULT_CONTROL_ENCODING);
            //ftp.storeFile(fileName, input);
            input.close();
            ftp.logout();
            System.out.println("结束本次上传");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 创建目录(有则切换目录,没有则创建目录)
     * @param dir
     * @return
     */
    public boolean createDir(String dir,FTPClient ftp){

        String d;
        try {
            //目录编码,解决中文路径问题
            d = new String(dir.toString().getBytes("GBK"),"iso-8859-1");
            //尝试切入目录
            if(ftp.changeWorkingDirectory(d))
                return true;

            String[] arr =  dir.split("/");
            StringBuffer sbfDir=new StringBuffer();
            //循环生成子目录
            for(String s : arr){
                sbfDir.append("/");
                sbfDir.append(s);
                //目录编码,解决中文路径问题
                d = new String(sbfDir.toString().getBytes("GBK"),"iso-8859-1");
                //尝试切入目录
                if(ftp.changeWorkingDirectory(d))
                    continue;
                if(!ftp.makeDirectory(d)){
                    System.out.println("[失败]ftp创建目录:"+sbfDir.toString());
                    return false;
                }
                System.out.println("[成功]创建ftp目录:"+sbfDir.toString());
            }
            //将目录切换至指定路径
            return ftp.changeWorkingDirectory(d);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

}

因为项目需要需要用到中文,工具类都附带了中文转换,如不需要可将其取消。

//ftp.setRemoteVerificationEnabled(false);  // 关闭远程IP校验
   ftp.setUseEPSVwithIPv4(true);

这两句代码费了好大劲找资料,在本项目都需要开启,第一个IP校验通过nginx代理后再经过FTP服务器返回的IP地址不一致再进行数据传输就会报错,所以需要开启。如果是第三方服务器可以直接在服务端禁用IP校验,windows的FTP服务暂时未找到此设置选项。  第二个是在复杂的网络环境下确保数据连接正常,项目所有思路都是通的,本地测试是在同一局域网但是只要一放到现场通过nginx代理转发上传文件就报错,差点推翻重来。。。

  1. EPSV (Extended Passive Mode):EPSV 是 FTP 的扩展被动模式。在 FTP 中,传输数据需要建立数据连接。标准的被动模式(PASV)使用端口 21 作为控制连接,然后服务器在另一个端口(通常是端口号大于1024)上打开数据连接。EPSV 是扩展的被动模式,它使用了更多的端口范围(如 IPv6 的范围),同时也能够支持 IPv4。

  2. 方法作用setUseEPSVwithIPv4(true) 方法告诉 FTP 客户端在使用 IPv4 时,也使用 EPSV 命令来建立数据连接。EPSV 命令可以在某些网络环境下提高 FTP 数据传输的可靠性和兼容性。如果你的 FTP 服务器支持 EPSV 命令,并且你的网络环境有可能受到防火墙或 NAT 网络设备的影响,这个设置可能会帮助你解决数据连接问题。

  3. 为什么要使用 EPSV:传统的被动模式(PASV)可能在防火墙或 NAT 设备后面导致连接失败,因为它涉及到服务器在动态端口上打开连接,这些端口可能被防火墙阻塞。EPSV 命令通过使用服务器控制端口 21 上的被动数据连接,可以避免这些问题,提高连接的成功率和稳定性。

三、nginx代理配置

stream {
        upstream ftp {  # FTP控制面转发
                server 10.8.0.194:21;
        }

        upstream ftpt1 {  # FTP控制面转发
                server 10.8.0.194:10001;
        }
upstream ftpt2 {  # FTP控制面转发
                server 10.8.0.194:10002;
        }

upstream ftpt3 {  # FTP控制面转发
                server 10.8.0.194:10003;
        }


        server {    # 转发FTP控制面请求
                listen 21;   #监听端口
                proxy_pass ftp;	
       }

        server {    # 转发FTP控制面请求
                listen 10001;   #监听端口
                proxy_pass ftpt1;	
       }
        server {    # 转发FTP控制面请求
                listen 10002;   #监听端口
                proxy_pass ftpt2;	
       }
        server {    # 转发FTP控制面请求
                listen 10003;   #监听端口
                proxy_pass ftpt3;	
       }
}

简约有效,刚开始只配置了21端口连接登录FTP,后边登录连接都没有问题然后上传文件一直报错,后边查资料了解FTP文件传输分为主动模式与被动模式,文件上传与登录连接是两个不同的链路,主动模式是客户端设定端口传输,被动模式是服务端随机分配端口告知客户端进行文件上传。需要详细了解的可以自行百度。 很奇怪的是本地测试不需要指定端口,只要一挂上vpn通过vpn的虚拟ip传输就要报错,后边通过指定被动模式IP范围并将指定的IP配置好nginx解决。

windows中自带的FTP服务在防火墙支持服务里边指定。以上大多是遇到的困难点、也方便自己日后再有需要参考记录,还有openvpn网络互通也有其他方式实现自行斟酌。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值