增加一个 SpringBoot 配置类:

  1. package *;
  2. import lombok.Getter;
  3. import lombok.Setter;
  4. import org.springframework.boot.context.properties.ConfigurationProperties;
  5. import org.springframework.context.annotation.Configuration;
  6. @Configuration
  7. @ConfigurationProperties(prefix = "ftp")
  8. @Getter
  9. @Setter
  10. public class FtpConfiguration {
  11. private String ip;
  12. private Integer port;
  13. private String username;
  14. private String password;
  15. private String workDir;
  16. }

配置文件中增加配置项:

  1. ftp:
  2. ip: 127.0.0.1
  3. port: 21
  4. username: username
  5. password: pass
  6. workDir: json

实现一个 Spring Component:

  1. package *;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.commons.net.ftp.FTP;
  4. import org.apache.commons.net.ftp.FTPClient;
  5. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  6. import org.springframework.stereotype.Component;
  7. import java.io.*;
  8. import java.nio.charset.StandardCharsets;
  9. /**
  10. * FTP 相关方法
  11. *
  12. * @author Paradise
  13. * @date 2021-9-23
  14. */
  15. @Slf4j
  16. @Component
  17. @EnableConfigurationProperties(FtpConfiguration.class)
  18. public class FtpClientComponent {
  19. private final FTPClient ftpClient;
  20. private final FtpConfiguration ftpConfiguration;
  21. /**
  22. * 本地文件缓存路径
  23. */
  24. private final String path = System.getProperty("user.dir") + File.separator + "json";
  25. public FtpClientComponent(FtpConfiguration ftpConfiguration) {
  26. this.ftpConfiguration = ftpConfiguration;
  27. ftpClient = new FTPClient();
  28. }
  29. public File generateJsonFile(String json, String fileName) throws IOException {
  30. log.info("开始生成本地文件:{}", fileName);
  31. File file = new File(path + File.separator + fileName + ".json");
  32. if (file.exists()) {
  33. return file;
  34. }
  35. if (!file.getParentFile().exists()) {
  36. final boolean mkdirRes = file.getParentFile().mkdirs();
  37. if (!mkdirRes) {
  38. return null;
  39. }
  40. }
  41. final boolean newFile = file.createNewFile();
  42. if (!newFile) {
  43. return null;
  44. }
  45. log.info("文件创建成功~");
  46. try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
  47. writer.write(json);
  48. writer.flush();
  49. }
  50. return file;
  51. }
  52. private void upload(File file) throws IOException {
  53. log.info("开始上传FTP文件");
  54. ftpClient.connect(ftpConfiguration.getIp(), ftpConfiguration.getPort());
  55. ftpClient.login(ftpConfiguration.getUsername(), ftpConfiguration.getPassword());
  56. log.info("ftpClient-isConnected : {} ", ftpClient.isConnected());
  57. log.info("ftpClient-isAvailable : {} ", ftpClient.isAvailable());
  58. final boolean changeWorkingDirectory = ftpClient.changeWorkingDirectory(ftpConfiguration.getWorkDir());
  59. log.info("changeWorkingDirectory : {} ", changeWorkingDirectory);
  60. final boolean setFileType = ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
  61. log.info("setFileType : {} ", setFileType);
  62. // 主动模式,解决假死问题
  63. ftpClient.enterLocalPassiveMode();
  64. log.info(" 开始上传: {} ", System.currentTimeMillis());
  65. final boolean uploadRes = ftpClient.storeFile(file.getName(), new FileInputStream(file));
  66. log.info("uploadRes : {} ", uploadRes);
  67. ftpClient.disconnect();
  68. }
  69. public boolean push(String json, String fileName) {
  70. File file;
  71. try {
  72. file = this.generateJsonFile(json, fileName);
  73. if (file != null) {
  74. this.upload(file);
  75. return true;
  76. }
  77. } catch (IOException e) {
  78. log.error(e.getLocalizedMessage(), e);
  79. }
  80. return false;
  81. }
  82. }

遇到假死问题,以及解决方案:

https://www.pianshen.com/article/75811462350/

防止链接失效,复制核心内容:

【解决办法】 调用 FTPClient.listFiles()或者FTPClient.retrieveFile()方法前,先调用一下FTPClient.enterLocalPassiveMode()

【原因分析】 FTP有两种模式:主动模式(active mode) 和 被动模式(passive mode)

默认情况下是启动的主动模式。

FTP 是 TCP 链接,所以在读取文件时,要进行三次握手。

我这里在进行调试的时候,FTP Server 的防火墙是关闭的,但是服务器 Tomcat 是在我本地 PC 跑的,防火墙是开启的,即 FTP Client 开防火墙了。

当 FTP 使用主动模式时,在三次握手后,FTP Client 会开启一个>1024 的端口,FTP Server 会开启默认端口20,并主动向 FTP Client 发起数据连接请求。

而此时受我电脑防火墙的限制,FTP Server 获得 FTP Client 的数据端口后,会主动发起数据连接请求,此时会被防火墙被屏蔽的。

FTP Client无法正常接收FTP Server发送来的数据流,所以就会出现假死的现象。

如果强制FTP使用被动模式,三次握手完成后,FTP Client 会开启一个>1024 的端口,并要求 FTP Server 开启一个端口(>1024),被动等待数据通道链接的开启。 当 FTP Client 开启数据传输通道时,FTP Server 就开始被动传输数据,不存在被墙住的问题,文件就可以正常下载下来了。

【csdn】ftp的主动模式active mode和被动模式 passive mode的配置和区别 https://blog.csdn.net/zhangyuan12805/article/details/71425385