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

PHP 文件上傳完整實戰:從表單構建到安全處理與 OOP 封裝

     編輯:PHP 相關知識

文件上傳核心要點

  • 表單必須設置 enctype="multipart/form-data" 且 method="POST"
  • 文件輸入框 <input type="file" name="myfile">
  • 服務端通過 $_FILES 超全局數組獲取文件信息
  • 必須驗證:文件大小、類型、上傳狀態、防止覆蓋、路徑遍歷

一、前端上傳表單 (upload.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文件上傳演示</title>
</head>
<body>
    <form action="uploadProcess.php" method="post" enctype="multipart/form-data">
        <table>
            <tr>
                <td>用戶名</td>
                <td><input type="text" name="username" required></td>
            </tr>
            <tr>
                <td>文件描述</td>
                <td><textarea name="fileintro" rows="5" cols="40"></textarea></td>
            </tr>
            <tr>
                <td>選擇文件</td>
                <td><input type="file" name="myfile" required></td>
            </tr>
            <tr>
                <td colspan="2"><input type="submit" value="上傳"></td>
            </tr>
        </table>
    </form>
</body>
</html>

二、後端處理腳本 (uploadProcess.php)

<?php
// 1. 接收表單數據
$username = $_POST['username'] ?? '';
$fileintro = $_POST['fileintro'] ?? '';

// 2. 基礎驗證
if (!isset($_FILES['myfile']) || $_FILES['myfile']['error'] !== UPLOAD_ERR_OK) {
    die("上傳失敗: " . ($_FILES['myfile']['error'] ?? '未知錯誤'));
}

$file = $_FILES['myfile'];

// 3. 文件大小限制 (2MB)
$maxSize = 2 * 1024 * 1024;
if ($file['size'] > $maxSize) {
    die("文件不能大於 2MB");
}

// 4. 文件類型驗證 (僅允許圖片)
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);

if (!in_array($mimeType, $allowedTypes)) {
    die("只允許上傳 JPG/PNG/GIF/WebP 圖片");
}

// 5. 確認是真實上傳文件
if (!is_uploaded_file($file['tmp_name'])) {
    die("非法上傳文件");
}

// 6. 構建保存路徑 (按用戶隔離目錄)
$baseDir = $_SERVER['DOCUMENT_ROOT'] . '/uploads/';
$userDir = $baseDir . $username . '/';

if (!is_dir($userDir)) {
    if (!mkdir($userDir, 0755, true)) {
        die("無法創建用戶目錄");
    }
}

// 7. 生成唯一文件名 (防止覆蓋)
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$newName = time() . '_' . mt_rand(1000, 9999) . '.' . $ext;
$destPath = $userDir . $newName;

// 8. 移動文件 (注意:Linux 不需 iconv 轉碼)
if (move_uploaded_file($file['tmp_name'], $destPath)) {
    echo "{$file['name']} 上傳成功!保存為: {$newName}";
    // 可在此記錄數據庫:用戶、原文件名、存儲路徑、大小、類型、描述等
} else {
    echo "文件移動失敗,請檢查目錄權限";
}
?>

三、OOP 封裝上傳類 (Upload.php)

<?php
class Upload {
    // 配置屬性
    private string $saveRoot;
    private array $allowedTypes;
    private int $maxSize;
    private array $allowedExts;

    // 文件信息屬性
    public string $originalName;
    public string $tmpPath;
    public int $size;
    public string $mimeType;
    public string $extension;
    public string $savedPath;
    public string $savedName;

    public function __construct(array $config = []) {
        $this->saveRoot = $config['save_root'] ?? $_SERVER['DOCUMENT_ROOT'] . '/uploads/';
        $this->maxSize = $config['max_size'] ?? 2 * 1024 * 1024; // 2MB
        $this->allowedTypes = $config['allowed_types'] ?? [
            'image/jpeg', 'image/png', 'image/gif', 'image/webp',
            'application/pdf', 'application/zip'
        ];
        $this->allowedExts = $config['allowed_exts'] ?? [
            'jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'zip', 'doc', 'docx', 'xls', 'xlsx'
        ];
    }

    // 設置上傳文件 (通常在實例化後調用)
    public function setFile(string $inputName): bool {
        if (!isset($_FILES[$inputName]) || $_FILES[$inputName]['error'] !== UPLOAD_ERR_OK) {
            return false;
        }
        $f = $_FILES[$inputName];
        $this->originalName = $f['name'];
        $this->tmpPath = $f['tmp_name'];
        $this->size = $f['size'];
        // 使用 finfo 獲取真實 MIME 類型
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $this->mimeType = finfo_file($finfo, $f['tmp_name']);
        finfo_close($finfo);
        $this->extension = strtolower(pathinfo($f['name'], PATHINFO_EXTENSION));
        return true;
    }

    // 執行上傳
    public function upload(string $subDir = ''): bool {
        // 1. 大小檢查
        if ($this->size > $this->maxSize) {
            throw new Exception("文件大小超過限制 ({$this->maxSize} bytes)");
        }

        // 2. 類型檢查 (雙重驗證)
        if (!in_array($this->mimeType, $this->allowedTypes) ||
            !in_array($this->extension, $this->allowedExts)) {
            throw new Exception("文件類型不允許: {$this->mimeType} / .{$this->extension}");
        }

        // 3. 確認上傳文件
        if (!is_uploaded_file($this->tmpPath)) {
            throw new Exception("非法上傳文件");
        }

        // 4. 目標目錄
        $targetDir = $this->saveRoot . $subDir . '/';
        if (!is_dir($targetDir)) {
            if (!mkdir($targetDir, 0755, true)) {
                throw new Exception("無法創建目錄: {$targetDir}");
            }
        }

        // 5. 生成唯一文件名
        $this->savedName = date('YmdHis') . '_' . mt_rand(10000, 99999) . '.' . $this->extension;
        $this->savedPath = $targetDir . $this->savedName;

        // 6. 移動文件
        if (!move_uploaded_file($this->tmpPath, $this->savedPath)) {
            throw new Exception("文件保存失敗,請檢查權限");
        }

        // 7. 設置權限 (可選)
        @chmod($this->savedPath, 0644);

        return true;
    }

    // 獲取文件信息數組
    public function getFileInfo(): array {
        return [
            'original_name' => $this->originalName,
            'saved_name'    => $this->savedName,
            'saved_path'    => $this->savedPath,
            'size'          => $this->size,
            'mime_type'     => $this->mimeType,
            'extension'     => $this->extension,
            'url'           => str_replace($_SERVER['DOCUMENT_ROOT'], '', $this->savedPath)
        ];
    }
}

// 使用示例
try {
    $upload = new Upload([
        'max_size' => 5 * 1024 * 1024, // 5MB
        'allowed_types' => ['image/jpeg', 'image/png', 'application/pdf'],
        'allowed_exts' => ['jpg', 'jpeg', 'png', 'pdf']
    ]);

    if ($upload->setFile('myfile')) {
        if ($upload->upload('user_' . ($_POST['username'] ?? 'guest'))) {
            $info = $upload->getFileInfo();
            echo "上傳成功!
"; echo "原始名: {$info['original_name']}
"; echo "保存名: {$info['saved_name']}
"; echo "訪問路徑: {$info['url']}
"; // 保存 $info 到數據庫... } } else { echo "未選擇文件或上傳出錯"; } } catch (Exception $e) { echo "上傳異常: " . $e->getMessage(); } ?>

四、安全加固清單

五、進階擴展建議

  • 分片上傳:大文件前端切片,後端合併 (支持斷點續傳)
  • 雲存儲對接:封裝 Storage 接口,實現 Local/Qiniu/ALI OSS/AWS S3 多驅動切換
  • 圖片處理管道:上傳後自動生成多規格縮略圖、水印、WebP 轉換
  • 異步處理:消息隊列解耦,上傳即返回,後台處理縮圖/掃毒/入庫
  • 病毒掃描:集成 ClamAV 或云端安全服務
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
風險點防護措施
腳本執行上傳目錄禁止腳本執行 (Nginx: location ~ \.php$ { deny all; } Apache: SetHandler default-handler)
文件類型偽裝使用 finfo 檢測真實 MIME,不信任 $_FILES['type']
文件名注入重命名存儲,不使用用戶原始文件名
路徑遍歷基於 basename()、pathinfo() 處理,絕不直接拼接用戶輸入
大文件 DoS限制 upload_max_filesize、post_max_size、max_file_uploads,Nginx client_max_body_size
圖片處理漏洞上傳後用 GD/Imagick 重新繪製保存,剝離潛在惡意代碼
權限過大目錄 0755,文件 0644,禁止 777