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

PHP FTP 操作類完整實現:上傳、下載、移動、複製、刪除、建目錄

     編輯:PHP 相關知識

類設計目標

  • 封裝 FTP 連接生命周期(連接、登錄、被動模式、關閉)
  • 提供統一的文件/目錄操作介面
  • 自動處理目標目錄不存在時的遞歸創建
  • 錯誤狀態標準化,便於上層業務判斷

完整類代碼

<?php
/**
 * FTP 操作類
 * 支持:上傳、下載、移動、複製、刪除、創建目錄、列表
 */
class FtpClient {
    private $conn;
    private $connected = false;
    public $lastError = '';

    /**
     * 構造函數:連接並登錄
     * @param string $host FTP 服務器地址
     * @param int $port 端口,默認 21
     * @param string $user 用戶名
     * @param string $pass 密碼
     * @param int $timeout 連接超時秒數
     * @throws Exception
     */
    public function __construct(string $host, int $port = 21, string $user, string $pass, int $timeout = 30) {
        $this->conn = @ftp_connect($host, $port, $timeout);
        if (!$this->conn) {
            throw new Exception("FTP 連接失敗:$host:$port");
        }
        if (!@ftp_login($this->conn, $user, $pass)) {
            $this->close();
            throw new Exception("FTP 登錄失敗:用戶名或密碼錯誤");
        }
        // 啟用被動模式(穿透防火牆/NAT)
        ftp_pasv($this->conn, true);
        $this->connected = true;
    }

    /** 檢查連接狀態 */
    private function ensureConnected(): void {
        if (!$this->connected) {
            throw new Exception('FTP 連接已關閉或未初始化');
        }
    }

    /**
     * 遞歸創建遠程目錄
     * @param string $remotePath 如 "a/b/c"
     * @return bool
     */
    public function mkdirRecursive(string $remotePath): bool {
        $this->ensureConnected();
        $dirs = explode('/', trim($remotePath, '/'));
        $current = '';
        
        foreach ($dirs as $dir) {
            $current .= "/$dir";
            // 嘗試進入,失敗則創建
            if (!@ftp_chdir($this->conn, $current)) {
                if (!@ftp_mkdir($this->conn, $current)) {
                    $this->lastError = "創建目錄失敗:$current";
                    return false;
                }
                if (!@ftp_chdir($this->conn, $current)) {
                    $this->lastError = "進入目錄失敗:$current";
                    return false;
                }
            }
        }
        // 返回根目錄
        @ftp_chdir($this->conn, '/');
        return true;
    }

    /**
     * 上傳文件
     * @param string $localPath 本地文件路徑
     * @param string $remotePath 遠程完整路徑(含文件名)
     * @param int $mode FTP_BINARY | FTP_ASCII
     * @return bool
     */
    public function upload(string $localPath, string $remotePath, int $mode = FTP_BINARY): bool {
        $this->ensureConnected();
        if (!is_file($localPath)) {
            $this->lastError = "本地文件不存在:$localPath";
            return false;
        }
        // 確保遠程目錄存在
        $remoteDir = dirname($remotePath);
        if ($remoteDir !== '.' && $remoteDir !== '/') {
            $this->mkdirRecursive($remoteDir);
        }
        $result = @ftp_put($this->conn, $remotePath, $localPath, $mode);
        if (!$result) $this->lastError = "上傳失敗:$localPath -> $remotePath";
        return $result;
    }

    /**
     * 下載文件
     * @param string $remotePath 遠程文件路徑
     * @param string $localPath 本地保存路徑
     * @param int $mode FTP_BINARY | FTP_ASCII
     * @return bool
     */
    public function download(string $remotePath, string $localPath, int $mode = FTP_BINARY): bool {
        $this->ensureConnected();
        $localDir = dirname($localPath);
        if (!is_dir($localDir)) mkdir($localDir, 0755, true);
        
        $result = @ftp_get($this->conn, $localPath, $remotePath, $mode);
        if (!$result) $this->lastError = "下載失敗:$remotePath -> $localPath";
        return $result;
    }

    /**
     * 移動/重命名文件(同服務器內)
     * @param string $from 來源路徑
     * @param string $to 目標路徑
     * @return bool
     */
    public function move(string $from, string $to): bool {
        $this->ensureConnected();
        $toDir = dirname($to);
        if ($toDir !== '.' && $toDir !== '/') {
            $this->mkdirRecursive($toDir);
        }
        $result = @ftp_rename($this->conn, $from, $to);
        if (!$result) $this->lastError = "移動失敗:$from -> $to";
        return $result;
    }

    /**
     * 複製文件(FTP 無原生複製,採用 下載->上傳)
     * @param string $from 來源路徑
     * @param string $to 目標路徑
     * @param string $tmpDir 本地臨時目錄
     * @return bool
     */
    public function copy(string $from, string $to, string $tmpDir = '/tmp'): bool {
        $this->ensureConnected();
        $tmpFile = $tmpDir . '/ftp_copy_' . uniqid() . '.tmp';
        
        if (!$this->download($from, $tmpFile)) return false;
        $result = $this->upload($tmpFile, $to);
        @unlink($tmpFile); // 清理臨時文件
        return $result;
    }

    /**
     * 刪除文件
     * @param string $remotePath
     * @return bool
     */
    public function delete(string $remotePath): bool {
        $this->ensureConnected();
        $result = @ftp_delete($this->conn, $remotePath);
        if (!$result) $this->lastError = "刪除失敗:$remotePath";
        return $result;
    }

    /**
     * 刪除目錄(遞歸)
     * @param string $remoteDir
     * @return bool
     */
    public function deleteDir(string $remoteDir): bool {
        $this->ensureConnected();
        $list = $this->listFiles($remoteDir);
        if ($list === false) return false;
        
        foreach ($list as $item) {
            if ($item['type'] === 'dir') {
                $this->deleteDir($item['path']);
            } else {
                $this->delete($item['path']);
            }
        }
        return @ftp_rmdir($this->conn, $remoteDir);
    }

    /**
     * 獲取文件列表
     * @param string $remoteDir
     * @return array|false [['name'=>'', 'path'=>'', 'type'=>'file|dir', 'size'=>0, 'time'=>0], ...]
     */
    public function listFiles(string $remoteDir = '.'): array|false {
        $this->ensureConnected();
        $raw = @ftp_nlist($this->conn, $remoteDir);
        if ($raw === false) {
            $this->lastError = "列表獲取失敗:$remoteDir";
            return false;
        }
        $result = [];
        foreach ($raw as $item) {
            $fullPath = rtrim($remoteDir, '/') . '/' . $item;
            // 簡易判斷類型:嘗試進入目錄
            $isDir = @ftp_chdir($this->conn, $fullPath);
            if ($isDir) @ftp_chdir($this->conn, $remoteDir); // 返回
            
            $result[] = [
                'name' => $item,
                'path' => $fullPath,
                'type' => $isDir ? 'dir' : 'file',
                'size' => $isDir ? 0 : (ftp_size($this->conn, $fullPath) ?: 0),
                'time' => ftp_mdtm($this->conn, $fullPath) ?: 0
            ];
        }
        return $result;
    }

    /** 獲取文件大小 */
    public function fileSize(string $remotePath): int {
        $this->ensureConnected();
        return ftp_size($this->conn, $remotePath) ?: -1;
    }

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

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

使用示例

<?php
require_once 'FtpClient.php';

try {
    $ftp = new FtpClient('ftp.example.com', 21, 'username', 'password');
    
    // 上傳
    $ftp->upload('/local/path/image.jpg', '/remote/images/2024/01/image.jpg');
    
    // 下載
    $ftp->download('/remote/backup.sql', '/local/backup/backup_' . date('Ymd') . '.sql');
    
    // 移動
    $ftp->move('/remote/temp/file.txt', '/remote/archive/file.txt');
    
    // 複製
    $ftp->copy('/remote/data/config.ini', '/remote/backup/config.ini');
    
    // 列表
    $files = $ftp->listFiles('/remote/images');
    foreach ($files as $f) {
        echo "{$f['name']} ({$f['type']}) {$f['size']} bytes\n";
    }
    
    // 刪除
    $ftp->delete('/remote/old/temp.log');
    
    $ftp->close();
} catch (Exception $e) {
    echo "FTP 錯誤:" . $e->getMessage();
}
?>

進階擴展建議

  1. SSL/TLS 支持:使用 ftp_ssl_connect() 實現 FTPS
  2. 斷點續傳:配合 ftp_fget()/ftp_fput() + ftp_seek() 實現大文件斷點續傳
  3. 並發控制:連接池模式複用連接,避免頻繁握手
  4. 事件回調:上傳進度、錯誤重試、日誌記錄鉤子
  5. 單元測試:用 vfsStream 或 Mock FTP Server 測試

常見坑點提醒

  • 被動模式必開:否則數據連接極大概率被防火牆攔截
  • 路徑分隔符統一用 /:Windows FTP Server 也接受正斜槓
  • 權限問題:上傳後可能需 ftp_chmod() 或 ftp_site("CHMOD 644 $file")
  • 編碼問題:中文文件名建議 URL 編碼或約定 UTF-8
  • 超時設置:大文件傳輸需調大 set_time_limit() 和 FTP 連接超時

這個類封裝了 FTP 核心操作,遵循單一職責原則,生產環境可直接使用或作為基礎擴展。

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