
本文详解如何在 PHP 中通过 FTP 安全、可靠地编辑远程服务器上的文本文件,重点解决 ftp_fput() 失败、file_exists() 误判路径、文件零字节等常见问题,并提供可直接复用的健壮实现方案。
本文详解如何在 php 中通过 ftp 安全、可靠地编辑远程服务器上的文本文件,重点解决 `ftp_fput()` 失败、`file_exists()` 误判路径、文件零字节等常见问题,并提供可直接复用的健壮实现方案。
在 Web 应用多站点协同场景中(如统一域名白名单管理),常需从不同子站动态更新部署在同一主站的共享配置文件(如 email_domain_whitelist.txt)。然而,直接使用 PHP 的 FTP 扩展操作远程文件时,开发者极易陷入以下典型误区:
- ❌ 错误地将 HTTP URL(如 https://example.com/...)传入 file_exists() 或 ftp_delete() —— 这些函数不支持远程 URL,仅作用于本地文件系统;
- ❌ 混淆 ftp_fput() 与 ftp_put():ftp_fput() 需传入已打开的 resource 句柄(如 fopen() 返回值),但若该句柄指向未正确写入或未关闭的临时文件,极易导致上传空文件(0 字节);
- ❌ 手动删除再上传——既增加失败风险,又非必要:ftp_put() 在目标路径存在同名文件时自动覆盖,无需显式调用 ftp_delete()。
✅ 正确做法是:本地生成临时文件 → 写入内容 → 关闭句柄 → 使用 ftp_put() 直接上传覆盖。以下是优化后的核心流程与可生产环境使用的完整代码示例:
✅ 推荐实现:安全、简洁、幂等
function eri_update_global_whitelist($string, $add_or_remove = 'add') {
// 1. 域名清洗与校验(保持原逻辑)
$domains = array_filter(
array_map('trim', explode(',', strtolower(str_replace(' ', '', $string)))),
function($d) { return strpos($d, '.') !== false; }
);
$invalid = array_diff(explode(',', strtolower(str_replace(' ', '', $string))), $domains);
if (empty($domains)) {
echo '<br>No valid domains to process.';
return;
}
$filename = 'email_domain_whitelist.txt';
$remote_path = '/eri-webtools-plugin/data/' . $filename; // 注意:这是 FTP 服务器上的相对路径(从根目录起)
// 2. 读取远程现有内容(通过 HTTP,仅用于读取)
$remote_url = 'https://example.com/eri-webtools-plugin/data/' . $filename;
$current_content = @file_get_contents($remote_url) ?: '';
$existing_domains = array_filter(
array_map('trim', explode(',', strtolower(str_replace(' ', '', $current_content)))),
function($d) { return strpos($d, '.') !== false; }
);
// 3. 合并/过滤逻辑
$new_domains = $add_or_remove === 'add'
? array_values(array_unique(array_merge($existing_domains, $domains)))
: array_values(array_diff($existing_domains, $domains));
// 4. 生成本地临时文件(关键!)
$local_temp_file = $filename; // 简单起见,使用同名;生产环境建议用 tempnam()
$fp = fopen($local_temp_file, 'w');
if (!$fp) {
echo '<br>Failed to create local temp file.';
return;
}
$content_to_write = implode(', ', $new_domains);
fwrite($fp, $content_to_write);
fclose($fp); // ⚠️ 必须关闭!否则 ftp_put 可能读取到空/缓存内容
// 5. FTP 连接与上传(自动覆盖)
require_once $_SERVER['DOCUMENT_ROOT'] . '/wp-content/plugins/eri-webtools-plugin/ftp_config.php';
$conn = ftp_connect($ftp_server);
if (!$conn) {
echo '<br>FTP connection failed.';
return;
}
$login = ftp_login($conn, $ftp_username, $ftp_userpass);
if (!$login) {
echo '<br>FTP login failed.';
ftp_close($conn);
return;
}
ftp_pasv($conn, true); // 启用被动模式(推荐)
// ✅ 核心修复:使用 ftp_put() + 本地文件路径(字符串),非 resource
if (ftp_put($conn, $remote_path, $local_temp_file, FTP_ASCII)) {
echo '<br>✅ Successfully updated remote whitelist: ' . count($new_domains) . ' domains.';
} else {
echo '<br>❌ FTP upload failed. Check permissions and path.';
}
ftp_close($conn);
// 6. 清理本地临时文件(可选但推荐)
@unlink($local_temp_file);
}? 关键注意事项总结
-
路径语义区分清晰:
- file_get_contents('https://...') ✅ 仅用于读取远程 HTTP 资源;
- ftp_put($conn, '/remote/path.txt', 'local_file.txt', ...) ✅ remote_path 是 FTP 服务器上的绝对路径(从根起),local_file.txt 是本地磁盘上的完整文件路径(字符串);
- file_exists('https://...') ❌ 永远返回 false,切勿使用。
-
资源管理铁律:
立即学习“PHP免费学习笔记(深入)”;
- fopen() 后必须 fclose(),否则 ftp_put() 可能因文件句柄未刷新而上传空内容;
- 临时文件建议使用 tempnam(sys_get_temp_dir(), 'whitelist_') 生成唯一路径,避免并发冲突。
-
错误处理增强建议(生产环境必备):
- 使用 ftp_ssl_connect() 替代 ftp_connect()(如服务器支持);
- 对 ftp_put() 失败添加重试机制与日志记录;
- 白名单更新前先备份远程文件(ftp_rename($remote_path, $remote_path . '.bak'))。
通过以上重构,你将获得一个稳定、可维护、符合最佳实践的远程配置文件编辑方案——不再依赖脆弱的 ftp_fput(),也不再为“文件找不到”或“上传空白”而深夜调试。











