YunaiV/yudao-cloud · error · FtpException

上传文件到目标目录 ({}) 失败

Error message

上传文件到目标目录 ({}) 失败

What it means

FtpFileClient.upload throws hutool FtpException after ftp.upload(dir, fileName, stream) returns false — meaning the FTP store refused the write. Hutool's Ftp returns a boolean instead of throwing, so this is the generic 'upload failed' signal: causes include permission denied on the target dir, missing/invalid basePath in the file client config, disk full, read-only or quota-limited FTP account, or a session that silently died despite reconnectIfTimeout().

Source

Thrown at yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java:56

    protected void doInit() {
        // 初始化 Ftp 对象:https://gitee.com/zhijiantianya/yudao-cloud/pulls/207/
        FtpConfig ftpConfig = new FtpConfig(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
                CharsetUtil.CHARSET_UTF_8, null, null);
        ftpConfig.setConnectionTimeout(CONNECTION_TIMEOUT);
        ftpConfig.setSoTimeout(SO_TIMEOUT);
        this.ftp = new Ftp(ftpConfig, FtpMode.valueOf(config.getMode()));
    }

    @Override
    public String upload(byte[] content, String path, String type) {
        // 执行写入
        String filePath = getFilePath(path);
        String fileName = FileUtil.getName(filePath);
        String dir = StrUtil.removeSuffix(filePath, fileName);
        reconnectIfTimeout();
        boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content)); // 不需要主动创建目录,ftp 内部已经处理(见源码)
        if (!success) {
            throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
        }
        // 拼接返回路径
        return super.formatFileUrl(config.getDomain(), path);
    }

    @Override
    public void delete(String path) {
        String filePath = getFilePath(path);
        reconnectIfTimeout();
        ftp.delFile(filePath);
    }

    @Override
    public byte[] getContent(String path) {
        String filePath = getFilePath(path);
        String fileName = FileUtil.getName(filePath);
        String dir = StrUtil.removeSuffix(filePath, fileName);
        ByteArrayOutputStream out = new ByteArrayOutputStream();

View on GitHub (pinned to 477be9dd49)

Solutions

  1. Verify the FTP account can write to the configured basePath (test with an FTP client or hutool Ftp.store manually)
  2. Match config mode to the network: Passive behind NAT/firewall, Active only on plain networks
  3. Check server-side disk space, quotas and SELinux/permission on the target dir
  4. Confirm config domain/basePath/username/password in infra file config are all correct (re-test connection in the admin UI)

Example fix

# before: active mode behind NAT
mode: Active

# after
mode: Passive   # matches firewall/NAT; verify write rights:
# lftp -u user,pass host -e "cd /upload && put test.txt; quit"
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: verify write access to basePath
try (Ftp probe = new Ftp(host, port, user, pass)) {
    if (!probe.cd(basePath) ) throw new IllegalStateException("basePath 不存在");
    if (!probe.upload(basePath, ".probe", new ByteArrayInputStream(new byte[1])))
        throw new IllegalStateException("FTP 目录不可写");
    probe.delFile(basePath + "/.probe");
}

Try / catch

try {
    return ftpFileClient.upload(content, path, type);
} catch (FtpException e) {
    log.warn("FTP upload failed for {}, retrying once", path, e);
    return ftpFileClient.upload(content, path, type); // transient data-channel failures often clear
}

Prevention

When it happens

Trigger: Uploading a file through infra file config #N of type FTP where the FTP user lacks write permission on basePath; basePath pointing to a non-writable directory; passive/port mode mismatch (FtpMode Passive/Active not matching the firewall) causing failed data connections; server out of space.

Common situations: Wrong FTP credentials/permissions after server migration; config mode left as Active behind NAT so the data channel fails; basePath with a typo; SELinux blocking writes on the FTP host.

Related errors


AI-assisted analysis of YunaiV/yudao-cloud@477be9dd49 (2026-08-14). Data as JSON: /api/errors/1adf1f6619fbbe27. Report an issue: GitHub.