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

PHP 多文件上傳經典類:支援單文件/多文件、類型驗證、隨機命名、錯誤處理

     編輯:PHP 相關知識

類特性概覽

  • 統一介面支援單文件 (ONE) 與多文件 (ANY) 上傳
  • 文件類型白名單、大小限制
  • 按年月自動創建上傳目錄
  • 隨機文件名防衝突 (TTR_時間戳_隨機字符.擴展名)
  • 鏈式調用風格,錯誤信息常量化管理
  • 析構函數自動清理屬性

完整類代碼 upload.class.php

<?php
/**
 * TTRUpload - 經典多文件上傳類
 * 使用示例見文末
 */
class TTRUpload {
    const MAX_SIZE = 81200000; // 80MB
    private string $uploadPath = 'uploadfile/';
    private string $savePath = '';
    private ?string $uploadedNames = null; // 單文件為字符串,多文件用 | 分隔
    private array $allowedExt = ['jpg', 'gif', 'png', 'jpeg', 'bmp', 'webp'];
    private ?string $error = null;
    private array $files = [];
    private string $mode = 'ONE'; // ONE | ANY

    public function __construct(array $files, string $mode = 'ONE') {
        if (!in_array($mode, ['ONE', 'ANY'], true)) {
            throw new InvalidArgumentException('模式必須為 ONE 或 ANY');
        }
        $this->mode = $mode;
        $this->files = $files;
    }

    /** 設置上傳根目錄 */
    public function setUploadPath(string $path): self {
        $this->uploadPath = rtrim($path, '/') . '/';
        return $this;
    }

    /** 執行上傳 */
    public function upload(): bool {
        if (!$this->prepareSavePath()) {
            return false;
        }

        if ($this->mode === 'ONE') {
            return $this->uploadSingle($this->files);
        } else {
            return $this->uploadMultiple($this->files);
        }
    }

    /** 獲取上傳後的文件名(多文件以 | 分隔) */
    public function getUploadFileName(): ?string {
        return $this->uploadedNames;
    }

    /** 獲取最後錯誤信息 */
    public function getError(): ?string {
        return $this->error;
    }

    /** 準備保存目錄 */
    private function prepareSavePath(): bool {
        $base = rtrim($this->uploadPath, '/');
        $this->savePath = $base . '/' . date('Ym') . '/';
        if (!is_dir($this->savePath)) {
            if (!mkdir($this->savePath, 0755, true)) {
                $this->error = '無法創建上傳目錄:' . $this->savePath;
                return false;
            }
        }
        return true;
    }

    /** 單文件上傳 */
    private function uploadSingle(array $file): bool {
        if (!$this->validateFile($file)) return false;

        $newName = $this->generateName($file['name']);
        $dest = $this->savePath . $newName;

        if (move_uploaded_file($file['tmp_name'], $dest)) {
            $this->uploadedNames = $newName;
            return true;
        }
        $this->error = '文件移動失敗';
        return false;
    }

    /** 多文件上傳 */
    private function uploadMultiple(array $files): bool {
        $names = [];
        foreach ($files as $file) {
            // 跳過空文件欄位
            if ($file['error'] === UPLOAD_ERR_NO_FILE) continue;

            if (!$this->validateFile($file)) return false;

            $newName = $this->generateName($file['name']);
            $dest = $this->savePath . $newName;

            if (!move_uploaded_file($file['tmp_name'], $dest)) {
                $this->error = '文件移動失敗:' . $file['name'];
                return false;
            }
            $names[] = $newName;
        }
        $this->uploadedNames = implode('|', $names);
        return true;
    }

    /** 文件驗證 */
    private function validateFile(array $file): bool {
        if ($file['error'] !== UPLOAD_ERR_OK) {
            $this->error = $this->getUploadErrorMsg($file['error']);
            return false;
        }

        $ext = strtolower($this->getExtension($file['name']));
        if (!in_array($ext, $this->allowedExt, true)) {
            $this->error = "不允許的文件類型:{$ext}";
            return false;
        }

        if ($file['size'] > self::MAX_SIZE) {
            $this->error = '文件大小超過限制 (80MB)';
            return false;
        }

        return true;
    }

    /** 生成唯一文件名 */
    private function generateName(string $originalName): string {
        $ext = $this->getExtension($originalName);
        $rand = $this->randomString(4);
        return 'TTR_' . time() . $rand . '.' . $ext;
    }

    /** 獲取擴展名 */
    private function getExtension(string $filename): string {
        return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    }

    /** 隨機字符串 */
    private function randomString(int $len): string {
        $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        for ($i = 0; $i < $len; $i++) {
            $str .= $chars[random_int(0, strlen($chars) - 1)];
        }
        return $str;
    }

    /** 上傳錯誤代碼轉中文 */
    private function getUploadErrorMsg(int $code): string {
        return match ($code) {
            UPLOAD_ERR_INI_SIZE => '超過 php.ini upload_max_filesize',
            UPLOAD_ERR_FORM_SIZE => '超過表單 MAX_FILE_SIZE',
            UPLOAD_ERR_PARTIAL => '文件僅部分上傳',
            UPLOAD_ERR_NO_FILE => '未選擇文件',
            UPLOAD_ERR_NO_TMP_DIR => '缺少臨時目錄',
            UPLOAD_ERR_CANT_WRITE => '寫入磁盤失敗',
            UPLOAD_ERR_EXTENSION => '被 PHP 擴展阻止',
            default => '未知上傳錯誤',
        };
    }

    public function __destruct() {
        $this->uploadedNames = null;
        $this->files = [];
        $this->savePath = '';
    }
}
?>

前端表單 index.html

<!DOCTYPE html>
<html lang="zh-Hant">
<head>
    <meta charset="UTF-8">
    <title>多文件上傳演示</title>
</head>
<body>
    <form action="upload.php" method="post" enctype="multipart/form-data">
        <input type="file" name="file1"><br>
        <input type="file" name="file2"><br>
        <input type="file" name="file3"><br>
        <input type="file" name="file4"><br>
        <button type="submit" name="button">上傳</button>
    </form>
</body>
</html>

處理頁面 upload.php

<?php
require_once 'upload.class.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['button'])) {
    // 整理文件數組
    $files = [
        $_FILES['file1'],
        $_FILES['file2'],
        $_FILES['file3'],
        $_FILES['file4'],
    ];

    $upload = new TTRUpload($files, 'ANY');
    // $upload->setUploadPath('custom/path/'); // 可選自定義路徑

    if ($upload->upload()) {
        echo "上傳成功!文件名:{$upload->getUploadFileName()}";
    } else {
        echo "上傳失敗:{$upload->getError()}";
    }
}
?>

單文件上傳用法

$upload = new TTRUpload($_FILES['file1'], 'ONE');
if ($upload->upload()) {
    echo "單文件上傳成功:{$upload->getUploadFileName()}";
}

安全加固建議

  1. 二次驗證圖片:上傳後調用 getimagesize() 或 imagetypes() 確認真實圖片格式。
  2. 禁用腳本執行:上傳目錄配置 Nginx location ~ \.php$ { deny all; } 或 Apache SetHandler default-handler。
  3. 文件名安全:生成名稱不使用用戶原始文件名任何部分,防止路徑遍歷。
  4. 病毒掃描:生產環境接入 ClamAV 或雲端安全 API 掃描上傳文件。
  5. 限流:結合 Redis 限制用戶上傳頻率(如每分鐘 10 次)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved