PHP ftp_append() 函数

ftp_append() 函数用于向 FTP 服务器上的文件追加内容。

该函数可以将数据追加到远程文件的末尾,如果文件不存在,则会创建新文件。与 ftp_put() 函数不同,ftp_append() 不会覆盖现有文件内容,而是在文件末尾添加新内容。

提示:该函数在 PHP 7.2.0 及更高版本中可用。如果使用的是早期版本,可以考虑使用其他方法实现类似功能。

语法

ftp_append ( resource $ftp_stream , string $remote_file , string $local_file [, int $mode = FTP_BINARY ] ) : bool

参数

参数 类型 描述
$ftp_stream resource 必需的。FTP 连接的标识符,由 ftp_connect()ftp_ssl_connect() 返回。
$remote_file string 必需的。远程文件的路径(在 FTP 服务器上),内容将被追加到此文件。
$local_file string 必需的。本地文件的路径,其内容将被追加到远程文件。
$mode int 可选的。传输模式。必须是 FTP_ASCIIFTP_BINARY。默认为 FTP_BINARY

返回值

返回值 描述
true 追加操作成功完成。
false 追加操作失败。可能的原因包括:
  • 本地文件不存在或不可读
  • 没有权限写入远程文件
  • FTP 连接已断开
  • 磁盘空间不足

示例

示例 1:基本使用 - 追加内容到远程文件

以下示例展示了如何向 FTP 服务器上的文件追加内容。

<?php
// 连接 FTP 服务器
$ftp_server = "ftp.example.com";
$ftp_user = "username";
$ftp_pass = "password";

$conn = ftp_connect($ftp_server);
if (!$conn) {
    die("无法连接到 $ftp_server");
}

// 登录
if (!@ftp_login($conn, $ftp_user, $ftp_pass)) {
    die("登录失败");
}

// 创建本地文件内容
$local_file = "log_entry.txt";
$log_content = "[" . date('Y-m-d H:i:s') . "] 用户登录成功\n";
file_put_contents($local_file, $log_content);

// 远程日志文件
$remote_file = "logs/application.log";

// 追加内容到远程文件
if (ftp_append($conn, $remote_file, $local_file, FTP_ASCII)) {
    echo "日志条目已成功追加到远程文件\n";

    // 获取文件大小以验证追加
    $file_size = ftp_size($conn, $remote_file);
    if ($file_size != -1) {
        echo "远程文件大小: " . $file_size . " 字节\n";
    }
} else {
    echo "追加内容失败\n";

    // 检查远程文件是否存在,如果不存在则创建
    if (ftp_size($conn, $remote_file) == -1) {
        echo "远程文件不存在,尝试创建新文件...\n";
        if (ftp_put($conn, $remote_file, $local_file, FTP_ASCII)) {
            echo "已创建新文件并写入内容\n";
        }
    }
}

// 清理本地临时文件
unlink($local_file);

// 关闭连接
ftp_close($conn);
?>

示例 2:追加字符串内容(不使用本地文件)

如果需要追加字符串而不是文件内容,可以先创建临时文件。

<?php
$conn = ftp_connect("ftp.example.com");
ftp_login($conn, "username", "password");

/**
 * 向远程文件追加字符串内容
 * @param resource $conn FTP连接
 * @param string $remote_file 远程文件路径
 * @param string $content 要追加的内容
 * @param int $mode 传输模式
 * @return bool 成功返回true,失败返回false
 */
function ftp_append_string($conn, $remote_file, $content, $mode = FTP_ASCII) {
    // 创建临时文件
    $temp_file = tempnam(sys_get_temp_dir(), 'ftp_append_');

    if ($temp_file === false) {
        return false;
    }

    // 写入内容到临时文件
    if (file_put_contents($temp_file, $content) === false) {
        unlink($temp_file);
        return false;
    }

    // 追加内容到远程文件
    $result = ftp_append($conn, $remote_file, $temp_file, $mode);

    // 清理临时文件
    unlink($temp_file);

    return $result;
}

// 使用示例
$remote_log = "logs/system.log";
$log_entry = sprintf("[%s] %s: %s\n",
    date('Y-m-d H:i:s'),
    'INFO',
    '系统运行正常,内存使用率: 45%'
);

if (ftp_append_string($conn, $remote_log, $log_entry, FTP_ASCII)) {
    echo "日志条目已成功追加\n";

    // 验证追加结果
    $temp_file = tempnam(sys_get_temp_dir(), 'ftp_verify_');
    if (ftp_get($conn, $temp_file, $remote_log, FTP_ASCII)) {
        $last_line = tail_file($temp_file, 1);
        echo "最后一条日志: " . trim($last_line) . "\n";
        unlink($temp_file);
    }
} else {
    echo "追加日志失败\n";
}

/**
 * 获取文件最后几行
 */
function tail_file($filename, $lines = 10) {
    $file_content = file($filename, FILE_IGNORE_NEW_LINES);
    if ($file_content === false) {
        return '';
    }

    $tail = array_slice($file_content, -$lines);
    return implode("\n", $tail);
}

ftp_close($conn);
?>

示例 3:批量追加多个文件内容

将多个本地文件的内容追加到同一个远程文件。

<?php
// 连接和登录
$conn = ftp_connect("ftp.example.com");
ftp_login($conn, "username", "password");

// 远程合并文件
$remote_merged_file = "data/merged_data.txt";

// 本地文件列表
$local_files = [
    "data/file1.txt",
    "data/file2.txt",
    "data/file3.txt"
];

echo "开始合并文件到远程服务器...\n";

foreach ($local_files as $local_file) {
    if (!file_exists($local_file)) {
        echo "跳过不存在的文件: $local_file\n";
        continue;
    }

    echo "处理文件: $local_file\n";

    if (ftp_append($conn, $remote_merged_file, $local_file, FTP_ASCII)) {
        $file_size = filesize($local_file);
        echo "  成功追加 ($file_size 字节)\n";
    } else {
        echo "  追加失败\n";

        // 如果是第一个文件且远程文件不存在,使用 ftp_put 创建文件
        if (ftp_size($conn, $remote_merged_file) == -1) {
            echo "  远程文件不存在,尝试创建...\n";
            if (ftp_put($conn, $remote_merged_file, $local_file, FTP_ASCII)) {
                echo "  文件创建成功\n";
            }
        }
    }
}

// 验证结果
$final_size = ftp_size($conn, $remote_merged_file);
if ($final_size != -1) {
    echo "\n合并完成!\n";
    echo "最终文件大小: $final_size 字节\n";

    // 计算预期的总大小
    $expected_size = 0;
    foreach ($local_files as $local_file) {
        if (file_exists($local_file)) {
            $expected_size += filesize($local_file);
        }
    }

    echo "预期总大小: $expected_size 字节\n";

    if ($final_size == $expected_size) {
        echo "✓ 文件大小匹配,合并成功!\n";
    } else {
        echo "⚠ 文件大小不匹配,可能存在数据丢失\n";
    }
}

ftp_close($conn);
?>

示例 4:错误处理和恢复

完整的错误处理机制,包括重试和恢复。

<?php
/**
 * 安全的 FTP 追加函数,包含错误处理和重试机制
 */
class SafeFTPAppend {
    private $conn;
    private $max_retries = 3;
    private $retry_delay = 1; // 秒

    public function __construct($server, $username, $password, $port = 21) {
        $this->conn = ftp_connect($server, $port, 30);
        if (!$this->conn) {
            throw new Exception("无法连接到 FTP 服务器");
        }

        if (!ftp_login($this->conn, $username, $password)) {
            throw new Exception("FTP 登录失败");
        }

        // 启用被动模式
        ftp_pasv($this->conn, true);
    }

    /**
     * 安全地追加内容到远程文件
     */
    public function safeAppend($remote_file, $local_file, $mode = FTP_BINARY) {
        $retry_count = 0;

        while ($retry_count < $this->max_retries) {
            try {
                // 检查本地文件
                if (!file_exists($local_file) || !is_readable($local_file)) {
                    throw new Exception("本地文件不存在或不可读: $local_file");
                }

                // 检查远程文件权限(如果存在)
                if (ftp_size($this->conn, $remote_file) != -1) {
                    // 文件存在,检查是否可写(通过尝试获取文件信息)
                    $file_info = @ftp_mdtm($this->conn, $remote_file);
                    if ($file_info === -1) {
                        throw new Exception("远程文件不可写或权限不足: $remote_file");
                    }
                }

                // 执行追加操作
                $result = ftp_append($this->conn, $remote_file, $local_file, $mode);

                if ($result) {
                    // 验证追加结果
                    $initial_size = $this->getRemoteFileSize($remote_file, true);
                    $local_size = filesize($local_file);
                    $expected_size = $initial_size + $local_size;

                    $final_size = $this->getRemoteFileSize($remote_file);

                    if ($final_size >= $expected_size) {
                        return [
                            'success' => true,
                            'message' => "内容追加成功",
                            'initial_size' => $initial_size,
                            'added_size' => $local_size,
                            'final_size' => $final_size,
                            'retries' => $retry_count
                        ];
                    } else {
                        throw new Exception("文件大小验证失败,可能追加不完整");
                    }
                } else {
                    throw new Exception("ftp_append() 返回 false");
                }

            } catch (Exception $e) {
                $retry_count++;

                if ($retry_count >= $this->max_retries) {
                    return [
                        'success' => false,
                        'message' => "追加失败,已达到最大重试次数",
                        'error' => $e->getMessage(),
                        'retries' => $retry_count
                    ];
                }

                echo "尝试 $retry_count 失败,{$this->retry_delay}秒后重试...\n";
                sleep($this->retry_delay);

                // 指数退避增加延迟
                $this->retry_delay *= 2;
            }
        }

        return [
            'success' => false,
            'message' => "未知错误",
            'retries' => $retry_count
        ];
    }

    /**
     * 获取远程文件大小,带缓存避免重复调用
     */
    private function getRemoteFileSize($remote_file, $use_cache = false) {
        static $size_cache = [];

        if ($use_cache && isset($size_cache[$remote_file])) {
            return $size_cache[$remote_file];
        }

        $size = ftp_size($this->conn, $remote_file);
        if ($size == -1) {
            $size = 0; // 文件不存在
        }

        if ($use_cache) {
            $size_cache[$remote_file] = $size;
        }

        return $size;
    }

    public function __destruct() {
        if ($this->conn) {
            ftp_close($this->conn);
        }
    }
}

// 使用示例
try {
    $ftp = new SafeFTPAppend('ftp.example.com', 'username', 'password');

    // 准备要追加的内容
    $local_file = 'data_to_append.txt';
    $data = "这是要追加的数据,生成时间: " . date('Y-m-d H:i:s') . "\n";
    file_put_contents($local_file, $data);

    // 执行安全追加
    $result = $ftp->safeAppend('logs/data.log', $local_file, FTP_ASCII);

    if ($result['success']) {
        echo "追加成功!\n";
        echo "初始大小: " . $result['initial_size'] . " 字节\n";
        echo "追加大小: " . $result['added_size'] . " 字节\n";
        echo "最终大小: " . $result['final_size'] . " 字节\n";
        echo "重试次数: " . $result['retries'] . "\n";
    } else {
        echo "追加失败: " . $result['message'] . "\n";
        if (isset($result['error'])) {
            echo "错误详情: " . $result['error'] . "\n";
        }
    }

    // 清理临时文件
    unlink($local_file);

} catch (Exception $e) {
    echo "FTP 操作异常: " . $e->getMessage() . "\n";
}
?>

注意事项

  • PHP版本要求:ftp_append() 函数需要 PHP 7.2.0 或更高版本。
  • 服务器支持:并非所有 FTP 服务器都支持追加操作。某些服务器可能需要特定配置。
  • 文件锁定:追加操作期间,其他进程可能同时写入同一文件,需要考虑并发控制。
  • 性能考虑:对于大文件,追加操作可能需要较长时间。建议设置适当的超时时间。
  • 传输模式:正确选择传输模式(FTP_ASCIIFTP_BINARY),特别是处理文本文件时。
  • 错误处理:始终检查函数的返回值,并实现适当的错误处理机制。
  • 临时文件:如果需要追加字符串内容,需要先创建临时文件,操作完成后记得清理。
  • 网络稳定性:FTP 传输可能受网络影响,建议实现重试机制。

常见错误和解决方案

错误/问题 可能的原因和解决方案
函数未定义 PHP版本低于7.2.0。升级PHP或使用ftp_fput()替代方案。
追加操作失败 服务器不支持追加操作。检查服务器配置或使用下载-修改-上传的方式。
权限被拒绝 FTP用户没有写入权限。检查服务器文件权限设置。
文件不存在错误 本地文件不存在。检查文件路径和权限。
传输不完整 网络中断或超时。增加超时时间或实现分块传输。
并发写入冲突 多个进程同时写入同一文件。实现文件锁定机制。

PHP 7.2以下版本的替代方案

如果您的PHP版本低于7.2.0,可以使用以下方法实现类似功能:

使用 ftp_fput() 实现追加功能

<?php
/**
 * PHP 7.2以下版本的ftp_append替代函数
 */
function ftp_append_compat($conn, $remote_file, $local_file, $mode = FTP_BINARY) {
    // 打开本地文件
    $handle = fopen($local_file, 'rb');
    if ($handle === false) {
        return false;
    }

    // 使用ftp_fput在追加模式下上传
    // 注意:这需要服务器支持 APPE 命令
    $result = ftp_fput($conn, $remote_file, $handle, $mode, FTP_APPEND);

    fclose($handle);
    return $result;
}

// 或者使用下载-修改-上传的方式
function ftp_append_manual($conn, $remote_file, $local_file, $mode = FTP_BINARY) {
    // 创建临时文件
    $temp_file = tempnam(sys_get_temp_dir(), 'ftp_');

    // 如果远程文件存在,先下载
    if (@ftp_size($conn, $remote_file) != -1) {
        if (!ftp_get($conn, $temp_file, $remote_file, $mode)) {
            unlink($temp_file);
            return false;
        }
    } else {
        // 文件不存在,创建空临时文件
        file_put_contents($temp_file, '');
    }

    // 追加本地文件内容
    $local_content = file_get_contents($local_file);
    file_put_contents($temp_file, $local_content, FILE_APPEND);

    // 上传回服务器
    $result = ftp_put($conn, $remote_file, $temp_file, $mode);

    // 清理临时文件
    unlink($temp_file);

    return $result;
}
?>

相关函数