首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP FTP 文件上傳操作指南

     編輯:PHP 相關知識

前言

FTP (File Transfer Protocol) 是服務器間傳輸文件的常用協議。PHP 內置 ftp_* 系列函數,無需第三方擴展即可實現連接、上傳、下載、目錄操作。本文提供一個封裝良好的上傳類與常見問題處理。

核心函數速查

函數用途
ftp_connect()建立連接
ftp_login()用戶名密碼登錄
ftp_pasv()設置被動模式(穿透防火牆/NAT 必須)
ftp_put()上傳文件 (FTP_ASCII / FTP_BINARY)
ftp_fput()從文件指針上傳(大文件流式)
ftp_mkdir() / ftp_chdir()創建/切換遠程目錄
ftp_close()關閉連接

完整上傳類實現

<?php
/**
 * PHP FTP 客戶端封裝
 * 支持鏈式調用、自動創建遠程目錄、錯誤異常拋出
 */
class FtpUploader {
    private $conn;
    private $config;

    public function __construct(array $config) {
        $this->config = array_merge([
            'host'     => 'ftp.example.com',
            'port'     => 21,
            'user'     => '',
            'pass'     => '',
            'pasv'     => true,      // 默認被動模式
            'timeout'  => 30,
            'ssl'      => false,     // true 使用 ftp_ssl_connect (FTPS)
        ], $config);
    }

    /** 連接並登錄 */
    public function connect(): self {
        $func = $this->config['ssl'] ? 'ftp_ssl_connect' : 'ftp_connect';
        $this->conn = @$func($this->config['host'], $this->config['port'], $this->config['timeout']);
        if (!$this->conn) {
            throw new RuntimeException("FTP 連接失敗: {$this->config['host']}:{$this->config['port']}");
        }
        if (!@ftp_login($this->conn, $this->config['user'], $this->config['pass'])) {
            throw new RuntimeException("FTP 登錄失敗: 用戶 {$this->config['user']}");
        }
        ftp_pasv($this->conn, $this->config['pasv']);
        return $this;
    }

    /** 確保遠程目錄存在,遞歸創建 */
    public function ensureDir(string $remotePath): self {
        $dirs = explode('/', trim($remotePath, '/'));
        $current = '';
        foreach ($dirs as $dir) {
            if (!$dir) continue;
            $current .= "/$dir";
            if (!@ftp_chdir($this->conn, $current)) {
                if (!@ftp_mkdir($this->conn, $current)) {
                    throw new RuntimeException("無法創建遠程目錄: $current");
                }
                ftp_chdir($this->conn, $current);
            }
        }
        return $this;
    }

    /**
     * 上傳單個文件
     * @param string $localPath  本地絕對路徑
     * @param string $remotePath 遠程相對路徑 (含文件名)
     * @param int    $mode       FTP_BINARY (默認) 或 FTP_ASCII
     */
    public function upload(string $localPath, string $remotePath, int $mode = FTP_BINARY): bool {
        if (!is_file($localPath)) {
            throw new InvalidArgumentException("本地文件不存在: $localPath");
        }
        // 確保遠程目錄存在
        $remoteDir = dirname($remotePath);
        if ($remoteDir !== '.') $this->ensureDir($remoteDir);

        $result = @ftp_put($this->conn, $remotePath, $localPath, $mode);
        if (!$result) {
            throw new RuntimeException("FTP 上傳失敗: $localPath => $remotePath");
        }
        return true;
    }

    /** 大文件流式上傳 (內存友好) */
    public function uploadStream(string $localPath, string $remotePath, int $mode = FTP_BINARY): bool {
        if (!is_file($localPath)) throw new InvalidArgumentException("文件不存在: $localPath");
        $remoteDir = dirname($remotePath);
        if ($remoteDir !== '.') $this->ensureDir($remoteDir);

        $fp = fopen($localPath, 'rb');
        if (!$fp) throw new RuntimeException("無法打開本地文件: $localPath");
        $result = @ftp_fput($this->conn, $remotePath, $fp, $mode);
        fclose($fp);
        if (!$result) throw new RuntimeException("FTP 流式上傳失敗: $remotePath");
        return true;
    }

    /** 關閉連接 */
    public function close(): void {
        if ($this->conn) {
            @ftp_close($this->conn);
            $this->conn = null;
        }
    }

    /** 析構函數保證關閉 */
    public function __destruct() {
        $this->close();
    }
}

// ===== 使用示例 =====
try {
    $ftp = new FtpUploader([
        'host' => '192.168.1.100',
        'user' => 'backup_user',
        'pass' => 'secret123',
        'pasv' => true,
    ])->connect();

    // 小文件直接上傳
    $ftp->upload('/data/export/report_202405.csv', 'reports/report_202405.csv');

    // 大文件 (如 > 100MB) 用流式
    $ftp->uploadStream('/data/backup/db_dump.sql.gz', 'backups/db_dump_202405.sql.gz');

    echo "所有文件上傳成功!\n";
} catch (Exception $e) {
    echo "上傳異常: " . $e->getMessage() . "\n";
    // 記錄日志、告警
}
?>

常見坑點與解決方案

  1. 主動模式 vs 被動模式:客戶端在 NAT/防火牆後必須啟用 ftp_pasv(true),否則數據連接無法建立(表現為連接成功但上傳卡死或超時)。
  2. 二進制 vs ASCII:默認使用 FTP_BINARY。僅純文本文件且跨系統 (Windows↔Linux) 換行符需轉換時用 FTP_ASCII。圖片、壓縮包、視頻必須二進制。
  3. 權限問題:FTP 用戶需對遠程目錄有寫入權限。部分服務器要求目錄權限 755、文件 644。
  4. 超時設置:大文件上傳需調大 ftp_connect 的 timeout,並設置 PHP set_time_limit(0) 或 ini_set('max_execution_time', 0)。
  5. FTPS (SSL/TLS):使用 ftp_ssl_connect() 並確保服務器支持隱式/顯式 FTPS。證書驗證問題可暫時關閉 stream_context_set_option(不推薦生產)。
  6. 錯誤信息獲取:ftp_put 返回 false 時,調用 error_get_last() 或檢查 FTP 服務器日志排查具體原因 (磁盤滿、配額超限、權限拒絕)。

進階:斷點續傳與並發

  • 斷點續傳:FTP 支持 REST 指令 (PHP 無原生封裝)。需用 ftp_raw($conn, "REST $offset") 然後 ftp_fput 從指定偏移追加。配合本地文件 fseek 實現。
  • 並發上傳:PHP 單進程串行。大量小文件可用 pcntl_fork、swoole、或 Guzzle + FTP 協議適配器 實現異步併發。

總結

PHP FTP 操作核心在於:被動模式、二進制傳輸、目錄預創建、異常處理、資源關閉。封裝為可復用類並結合日志監控,即可構建穩健的文件分發、備份同步系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved