{"record":{"id":"1adf1f6619fbbe27","repo":"YunaiV/yudao-cloud","slug":"error-1adf1f","errorCode":null,"errorMessage":"上传文件到目标目录 ({}) 失败","messagePattern":"上传文件到目标目录 \\((.+?)\\) 失败","errorType":"exception","errorClass":"FtpException","httpStatus":500,"severity":"error","filePath":"yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java","lineNumber":56,"sourceCode":"    protected void doInit() {\n        // 初始化 Ftp 对象：https://gitee.com/zhijiantianya/yudao-cloud/pulls/207/\n        FtpConfig ftpConfig = new FtpConfig(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),\n                CharsetUtil.CHARSET_UTF_8, null, null);\n        ftpConfig.setConnectionTimeout(CONNECTION_TIMEOUT);\n        ftpConfig.setSoTimeout(SO_TIMEOUT);\n        this.ftp = new Ftp(ftpConfig, FtpMode.valueOf(config.getMode()));\n    }\n\n    @Override\n    public String upload(byte[] content, String path, String type) {\n        // 执行写入\n        String filePath = getFilePath(path);\n        String fileName = FileUtil.getName(filePath);\n        String dir = StrUtil.removeSuffix(filePath, fileName);\n        reconnectIfTimeout();\n        boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content)); // 不需要主动创建目录，ftp 内部已经处理（见源码）\n        if (!success) {\n            throw new FtpException(StrUtil.format(\"上传文件到目标目录 ({}) 失败\", filePath));\n        }\n        // 拼接返回路径\n        return super.formatFileUrl(config.getDomain(), path);\n    }\n\n    @Override\n    public void delete(String path) {\n        String filePath = getFilePath(path);\n        reconnectIfTimeout();\n        ftp.delFile(filePath);\n    }\n\n    @Override\n    public byte[] getContent(String path) {\n        String filePath = getFilePath(path);\n        String fileName = FileUtil.getName(filePath);\n        String dir = StrUtil.removeSuffix(filePath, fileName);\n        ByteArrayOutputStream out = new ByteArrayOutputStream();","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/YunaiV/yudao-cloud/blob/477be9dd49ab7223a972a6abdff0684d6423dec3/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java#L38-L74","documentation":"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().","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the FTP account can write to the configured basePath (test with an FTP client or hutool Ftp.store manually)","Match config mode to the network: Passive behind NAT/firewall, Active only on plain networks","Check server-side disk space, quotas and SELinux/permission on the target dir","Confirm config domain/basePath/username/password in infra file config are all correct (re-test connection in the admin UI)"],"exampleFix":"# before: active mode behind NAT\nmode: Active\n\n# after\nmode: Passive   # matches firewall/NAT; verify write rights:\n# lftp -u user,pass host -e \"cd /upload && put test.txt; quit\"","handlingStrategy":"retry","validationCode":"// pre-flight: verify write access to basePath\ntry (Ftp probe = new Ftp(host, port, user, pass)) {\n    if (!probe.cd(basePath) ) throw new IllegalStateException(\"basePath 不存在\");\n    if (!probe.upload(basePath, \".probe\", new ByteArrayInputStream(new byte[1])))\n        throw new IllegalStateException(\"FTP 目录不可写\");\n    probe.delFile(basePath + \"/.probe\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    return ftpFileClient.upload(content, path, type);\n} catch (FtpException e) {\n    log.warn(\"FTP upload failed for {}, retrying once\", path, e);\n    return ftpFileClient.upload(content, path, type); // transient data-channel failures often clear\n}","preventionTips":["Use Passive mode behind NAT/firewalls; Active only on open networks","Verify FTP write permissions during file-config save (test upload)","Monitor disk space on the FTP host","Keep reconnectIfTimeout paths covered with integration tests against a test FTP server"],"tags":["infra","file","ftp","upload","network","permissions"],"backgroundTag":null,"analyzedSha":"477be9dd49ab7223a972a6abdff0684d6423dec3","analyzedAt":"2026-08-14T13:35:31.121Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}