You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot FTP功能本地正常,Docker部署后异常求助

Spring Boot FTP接口Docker部署异常排查

本地环境调用外部FTP服务正常,部署到Debian VPS的Docker容器后出现以下问题:

  • /api/getfile接口返回200状态码但无数据返回
  • /api/uploadfile接口返回false

控制器代码(FileController.java)

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

@CrossOrigin
@RestController
public class FileController {

    FtpUtil ftpUtil;

    @Scheduled(fixedDelay = 5000)
    @PostMapping("/api/uploadfile")
    public boolean uploadFile(@RequestParam(value ="file") MultipartFile file) throws IOException {
        InputStream inputStream = file.getInputStream();
        return ftpUtil.uploadFile("ftp.host.fr",21,"user","password","./assets/images/jej", "/",file.getOriginalFilename(), inputStream);
    }

    @Scheduled(fixedDelay = 5000)
    @GetMapping("/api/getfile")
    public List<String> getFileList() {
        return ftpUtil.udownLoadFile("host",21,"user","pass","./assets/images/jej");
    }
}

FTP工具类代码(FtpUtil.java)

import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

public class FtpUtil {

    public static boolean uploadFile(String host, int port, String username, String password,
                                     String basePath, String filePath, String filename, InputStream input) {
        FTPClient ftp = new FTPClient();
        Boolean result = false;
        try {
            ftp.connect(host, port);
            ftp.login(username, password);
            int reply = ftp.getReplyCode();
            if (!FTPReply.isPositiveCompletion(reply)) {
                ftp.disconnect();
                return result;
            }

            String path = basePath + filePath;
            if (!ftp.changeWorkingDirectory(path)) {
                String[] dirs = path.split("/");
                StringBuffer tempPath = new StringBuffer();
                for (String dir : dirs) {
                    if (null == dir || "".equals(dir)) {
                        continue;
                    }
                    tempPath.append("/").append(dir);
                    if (!ftp.changeWorkingDirectory(tempPath.toString())) {
                        if (!ftp.makeDirectory(tempPath.toString())) {
                            return result;
                        } else {
                            ftp.changeWorkingDirectory(tempPath.toString());
                        }
                    }
                }
            }

            ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
            if (!ftp.storeFile(filename, input)) {
                return result;
            }
            input.close();
            ftp.logout();
            result = true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException ioe) {
                }
            }
        }
        return result;
    }

    public static List<String> udownLoadFile(String host, int port, String username, String password,
                                             String remotePath) {
        FTPClient ftp = new FTPClient();
        try {
            int reply;
            ftp.connect(host, port);
            ftp.login(username, password);
            reply = ftp.getReplyCode();
            if (!FTPReply.isPositiveCompletion(reply)) {
                ftp.disconnect();
            }
            ftp.changeWorkingDirectory(remotePath);
            FTPFile[] fs = ftp.listFiles();
            return Arrays.stream(fs)
                    .map(FTPFile::getName)
                    .collect(Collectors.toList());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException ioe) {
                }
            }
        }
        return null;
    }
}

Docker运行命令

docker container run --network jejnet --name je2 -p 8080:8080 -v slaateejej-volume -d slaatee/jej:trya

排查方向与解决步骤

1. 网络连通性验证

Docker容器默认使用桥接网络,需确认容器能正常访问外部FTP服务器:

  • 进入容器执行ping ftp.host.fr,检查DNS解析与网络连通性
  • 执行telnet ftp.host.fr 21,验证FTP端口是否可访问
  • 若使用自定义网络jejnet,确认该网络的防火墙规则允许出站访问21端口

2. 配置FTP被动模式

多数FTP服务器默认使用被动模式,容器内未配置会导致数据传输失败:

  • 在FtpUtil的ftp.login()后添加被动模式配置:
    ftp.enterLocalPassiveMode();
    
  • 若FTP服务器指定了被动端口范围,需在Docker运行命令中映射对应端口(如-p 21000-21010:21000-21010),并在VPS防火墙开放这些端口

3. 修正文件路径问题

代码中使用相对路径./assets/images/jej,容器内工作目录可能与本地不一致:

  • 改为FTP服务器的绝对路径(如/assets/images/jej)
  • 或在ftp.changeWorkingDirectory前打印当前工作目录,确认路径有效性

4. 完善异常日志

目前仅控制台打印异常,容器日志可能未捕获关键错误:

  • 使用SLF4J等日志框架将异常信息写入日志文件
  • 执行docker logs je2查看容器运行日志,定位具体错误

5. 权限检查

  • 确认FTP账号对目标目录有读写权限
  • 检查容器内应用运行用户是否有足够权限操作上传输入流

内容的提问来源于stack exchange,提问作者Sylvain Le Bren

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 18:39:41