SpringBoot集成FTP上传文件功能实现

 更新时间:2025年08月22日 10:06:07   作者:齐 飞  
文章介绍了FTP和SFTP的区别,强调SFTP基于SSH加密更安全,且使用单一端口,同时说明Spring Boot集成FTP上传需配置依赖、文件类、对象池及懒加载机制,以实现高效文件传输与资源管理,本文重点给大家介绍SpringBoot集成FTP上传文件功能实现,感兴趣的朋友一起看看吧

FTP是什么?

文件传输协议(英语:File Transfer Protocol,缩写:FTP)是一个用于在计算机网络上在客户端和服务器之间进行文件传输的应用层协议。

SFTP 和 FTP 的区别

SFTP 和 FTP 是两种不同的文件传输协议,它们在安全性、连接方式和功能上有显著差异。

FTP(File Transfer Protocol)

  • 使用明文传输数据,包括用户名和密码。
  • 需要两个端口:控制端口(21)和数据端口(20)。
  • 不支持加密,容易被中间人攻击。
  • 支持匿名登录和主动/被动模式。

SFTP(SSH File Transfer Protocol)

  • 基于 SSH 协议,所有数据传输均加密。
  • 仅使用单一端口(默认 22),简化防火墙配置。
  • 支持文件操作(如重命名、删除)和目录列表。
  • 无需额外配置,依赖 SSH 服务即可使用。

SpringBoot集成FTP上传文件

pom依赖

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

配置文件

ftp:
  host: host.test
  port: 21
  username: ftptest01
  password: xxxxxx
  path: /var/ftp/test/
  outlink: https://host.test
  filePath: /test/
  encoding: utf-8
  maxActive: 100
  minIdel: 2
  maxIdel: 5
  maxWaitMillis: 3000
  passivemode: true

配置类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * @author qf
 * @version 1.0
 * @data 2025/7/20 18:28
 */
@Data
@Component
@ConfigurationProperties(prefix = "ftp")
public class FTPConfig {
    private String host;
    private Integer port;
    private String username;
    private String password;
    private String encoding;
    private Integer maxActive;
    private Integer minIdel;
    private Integer maxIdel;
    private Integer maxWaitMillis;
    private Boolean passivemode;
    private String outlink;
    private String filePath;
    private String path;
}

对象池的创建与管理

import lombok.extern.log4j.Log4j2;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
 * @author qf
 * @version 1.0
 * @data 2025/7/20 18:34
 */
@Log4j2
@Component
public class FtpClientFactory implements PooledObjectFactory<FTPClient> {
    @Autowired
    private FTPConfig ftpConfig;
    /**
     * 创建连接放入池中
     *
     * @return
     * @throws Exception
     */
    @Override
    public PooledObject<FTPClient> makeObject() throws Exception {
        FTPClient ftpClient = new FTPClient();
        //ftpClient.setControlEncoding(ftpConfig.getEncoding());
        ftpClient.setConnectTimeout(ftpConfig.getMaxWaitMillis());
        try {
            ftpClient.connect(ftpConfig.getHost(), ftpConfig.getPort());
            int replyCode = ftpClient.getReplyCode();
            if (!FTPReply.isPositiveCompletion(replyCode)) {
                ftpClient.disconnect();
                log.warn("FTP服务拒绝链接");
                return null;
            }
            boolean login = ftpClient.login(ftpConfig.getUsername(), ftpConfig.getPassword());
            if (!login) {
                log.warn("ftpClient登入失败,username:{" + ftpConfig.getUsername() + "},password:{" + ftpConfig.getPassword() + "}");
                throw new RuntimeException("ftpClient登入失败,username:{" + ftpConfig.getUsername() + "},password:{" + ftpConfig.getPassword() + "}");
            }
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            ftpClient.setBufferSize(1024);
            if (ftpConfig.getPassivemode()) {
                ftpClient.enterLocalPassiveMode();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info("添加一个FtpClient进入连接池");
        return new DefaultPooledObject<>(ftpClient);
    }
    @Override
    public void destroyObject(PooledObject<FTPClient> pooledObject) throws Exception {
        FTPClient ftpClient = pooledObject.getObject();
        try {
            if (ftpClient != null && ftpClient.isConnected()) {
                ftpClient.logout();
            }
        } catch (IOException e) {
            throw new RuntimeException("没有获取到FtpClient或已断开连接:", e);
        } finally {
            try {
                ftpClient.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 连接状态检查
     *
     * @param pooledObject
     * @return
     */
    @Override
    public boolean validateObject(PooledObject<FTPClient> pooledObject) {
        FTPClient ftpClient = pooledObject.getObject();
        try {
            return ftpClient.sendNoOp();
        } catch (IOException e) {
            log.info("无法验证FtpClient {}", e.getMessage());
            return false;
        }
    }
    @Override
    public void activateObject(PooledObject<FTPClient> pooledObject) throws Exception {
    }
    @Override
    public void passivateObject(PooledObject<FTPClient> pooledObject) throws Exception {
    }
    public FTPConfig getConfig() {
        return this.ftpConfig;
    }
}

PooledObjectFactory 是 Apache Commons Pool 2 库中的一个核心接口,它定义了用于创建和管理池化对象(即那些可以被多个客户端重复使用的对象)生命周期的方法。通过实现这个接口,可以定制化对象的创建、激活、验证和销毁等过程,从而更好地控制对象池的行为。
其中:

  • PooledObject makeObject() throws Exception
  • 创建一个新的实例,并将其包装在一个 PooledObject 实例中返回。这是用来生成新的池对象的方法。
  • void destroyObject(PooledObject p) throws Exception
  • 销毁指定的池对象。当对象池决定不再需要某个对象时,会调用此方法来清理资源。
  • boolean validateObject(PooledObject p)
  • 验证池中的某个对象是否仍然有效。如果返回 true,则认为该对象可以继续使用;否则,可能需要重新创建或销毁该对象。
  • void activateObject(PooledObject p) throws Exception
  • 当从池中借用对象之前调用,用于“激活”对象,比如重置状态等操作。
  • void passivateObject(PooledObject p) throws Exception

当对象归还到池后调用,用于将对象置于“钝化”状态,通常涉及清理工作以准备下次使用。

FTP连接池初始化

import lombok.extern.log4j.Log4j2;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
 * @author qf
 * @version 1.0
 * @data 2025/7/20 18:47
 */
@Lazy
@Log4j2
@Component
public class FtpClientPool {
    private FtpClientFactory factory;
    private final GenericObjectPool<FTPClient> internalPool;
    /**
     * 初始化连接池
     */
    public FtpClientPool(@Autowired FtpClientFactory factory) {
        log.info("**********************初始化Ftp连接池*********************");
        this.factory = factory;
        FTPConfig config = factory.getConfig();
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxTotal(config.getMaxActive());
        poolConfig.setMinIdle(config.getMinIdel());
        poolConfig.setMaxIdle(config.getMaxIdel());
        poolConfig.setMaxWaitMillis(config.getMaxWaitMillis());
        this.internalPool = new GenericObjectPool<FTPClient>(factory, poolConfig);
        this.internalPool.setTestOnBorrow(true);
        log.info("连接池配置:{}", config.toString());
        log.info("*********************初始化Ftp连接池完毕*******************");
    }
    /**
     * 获取FTPClient
     * @return
     */
    public FTPClient getFTPClient(){
        try {
            log.info("从连接池中获取FtpClient");
            return internalPool.borrowObject();
        } catch (Exception e) {
            log.error("从连接池获取FTPClient失败!");
            return null;
        }
    }
    /**
     * 归还连接
     * @param ftpClient
     */
    public void returnFTPClient(FTPClient ftpClient) {
        try {
            ftpClient.getStatus();
            log.info("归还FtpClient:" + ftpClient);
            internalPool.returnObject(ftpClient);
        } catch (IOException e) {
            log.info("移除过期ftpClient {}", e.getMessage());
            try {
                internalPool.invalidateObject(ftpClient);
            } catch (Exception ex) {
                log.info("移除过期ftpClient失败 {}", e.getMessage());
            }
        }
    }
    /**
     * 销毁连接池
     */
    public void destory() {
        log.info("*********************close FtpPool**********************");
        internalPool.close();
    }
}

FTP工具类

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
/**
 * @author qf
 * @version 1.0
 * @data 2025/7/20 18:47
 */
@Lazy
@Slf4j
@Component
public class FTPClientUtil {
    @Autowired
    private FtpClientPool pool;
    /**
     * 上传文件
     *
     * @param file
     * @param path
     * @return
     * @throws IOException
     */
    public Boolean upload(MultipartFile file, String path) throws IOException {
        FTPClient ftpClient = pool.getFTPClient();
        InputStream inputStream = null;
        Boolean result = false;
        try {
            inputStream = file.getInputStream();
            createDir(ftpClient, path);
            result = ftpClient.storeFile(path, inputStream);
            log.info("文件{},服务器路径{},上传结果result:{}", file.getOriginalFilename(), path, result);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            inputStream.close();
            pool.returnFTPClient(ftpClient);
        }
        return result;
    }
    /**
     * 创建文件夹
     *
     * @param ftpClient
     * @param path
     * @return
     * @throws IOException
     */
    private Boolean createDir(FTPClient ftpClient, String path) throws IOException {
        Boolean flag = false;
        String substring = path.substring(path.indexOf("/") + 1, path.lastIndexOf("/"));
        ftpClient.changeWorkingDirectory("/");
        //boolean b = ftpClient.changeWorkingDirectory(substring);
        String[] split = substring.split("/");
        for (String s : split) {
            flag = ftpClient.makeDirectory(s);
            boolean changeFlag=ftpClient.changeWorkingDirectory(s);
            log.info("目录:{},创建目录结果:{},切换目录结果:{}",s,flag,changeFlag);
        }
        ftpClient.changeWorkingDirectory("/");
        return flag;
    }
}

这里因为项目部署在多个服务器中,而有一些环境不使用ftp,因此使用懒加载的方式。
示例:

     @Lazy
     @Autowired
     private FTPClientUtil ftpClientUtil;

Controller

import com.qf.util.ftp.FTPClientUtil;
import com.qf.util.ftp.FTPConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
/**
 * @author qf
 * @version 1.0
 * @data 2025/7/20 19:36
 */
@Slf4j
@RestController
@RequestMapping("api")
public class TestController {
    @Autowired
    private FTPConfig ftpConfig;
    @Autowired
    private FTPClientUtil ftpClientUtil;
    @PostMapping(value = "/uploadExcelFile",headers = "content-type=multipart/form-data")
    public Boolean uploadImgFile(@RequestParam("file") MultipartFile file) throws IOException {
        String path = ftpConfig.getPath();
        String originalFilename = file.getOriginalFilename();
        String fileSuffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        String fileId = UUID.randomUUID().toString().replace("-", "");
        String newFileName = fileId + fileSuffix;
        String filePath = path + newFileName;
        Boolean flag = ftpClientUtil.upload(file, filePath);
        return flag;
    }
}

到此这篇关于SpringBoot集成FTP上传文件功能实现的文章就介绍到这了,更多相关springboot ftp上传文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JAVA程序内存溢出问题原因分析

    JAVA程序内存溢出问题原因分析

    这篇文章主要介绍了JAVA程序内存溢出问题原因,较为详细的分析java导致程序内存溢出的原因与解决方法,需要的朋友可以参考下
    2015-06-06
  • 使用Java构造和解析Json数据的两种方法(详解一)

    使用Java构造和解析Json数据的两种方法(详解一)

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,是理想的数据交换格式。接下来通过本文给大家介绍使用Java构造和解析Json数据的两种方法,需要的朋友参考下吧
    2016-03-03
  • mybatis动态拼接实现有条件的插入

    mybatis动态拼接实现有条件的插入

    这篇文章主要介绍了mybatis动态拼接实现有条件的插入,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • JAVA各种加密与解密方式总结大全

    JAVA各种加密与解密方式总结大全

    这篇文章主要给大家介绍了关于JAVA各种加密与解密方式总结的相关资料,加密是指对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • IDEA配置Maven教程的超详细讲解版

    IDEA配置Maven教程的超详细讲解版

    IntelliJ IDEA是当前最流行的Java IDE(集成开发环境)之一,也是业界公认最好用的Java开发工具之一,这篇文章主要给大家介绍了关于IDEA配置Maven教程的超详细讲解版,需要的朋友可以参考下
    2023-11-11
  • Java实现Base64图片转URL的完整方案

    Java实现Base64图片转URL的完整方案

    这篇文章主要介绍了如何使用Java实现Base64格式图片转换为可访问的URL链接的过程,包括数据有效性校验、图片压缩、方向信息校正以及阿里云OSS存储对接,核心实现代码涵盖了从获取Base64图片数据到上传OSS的完整流程,需要的朋友可以参考下
    2025-11-11
  • IDEA maven依赖错误中包下面红色波浪线

    IDEA maven依赖错误中包下面红色波浪线

    这篇文章主要介绍了IDEA maven依赖错误中包下面红色波浪线,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • 解决SpringBoot请求返回字符串中文乱码的问题

    解决SpringBoot请求返回字符串中文乱码的问题

    这篇文章主要介绍了解决SpringBoot请求返回字符串中文乱码的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • Feign 使用HttpClient和OkHttp方式

    Feign 使用HttpClient和OkHttp方式

    这篇文章主要介绍了Feign 使用HttpClient和OkHttp方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Spring Boot 3.5.3新特性解析及JDK21集成实战应用

    Spring Boot 3.5.3新特性解析及JDK21集成实战应用

    Spring Boot 3.5.3作为关键补丁版本,不仅修复了此前版本中的棘手回归问题,更深度整合JDK21特性,为高并发与云原生应用带来突破性提升,本文将深入解析其核心技术亮点及实战应用,感兴趣的朋友跟随小编一起看看吧
    2025-08-08

最新评论