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

PHP 圖片上傳類完整實現:驗證、重命名、存儲、資料庫記錄一站式封裝

     編輯:PHP 相關知識

圖片上傳是 Web 開發高頻需求,涉及安全驗證、文件命名、存儲路徑、數據庫關聯。本文提供一個生產就緒的 ImageUpload 類,包含類型/大小驗證、安全重命名、自動建目錄、錯誤碼規範、配合資料庫記錄的完整流程。

核心類實現

<?php
declare(strict_types=1);

class ImageUpload {
    private array $allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
    private int $maxSize = 2 * 1024 * 1024; // 2MB
    private string $saveRoot;
    private string $urlPrefix;

    public function __construct(string $saveRoot, string $urlPrefix = '/uploads/') {
        $this->saveRoot = rtrim($saveRoot, '/');
        $this->urlPrefix = rtrim($urlPrefix, '/');
    }

    public function setMaxSize(int $bytes): self { $this->maxSize = $bytes; return $this; }
    public function setAllowedMimes(array $mimes): self { $this->allowedMimes = $mimes; return $this; }

    /**
     * 上傳單個文件
     * @param array $file $_FILES['field'] 子數組
     * @return array ['success'=>bool, 'url'=>string, 'path'=>string, 'error'=>string, 'code'=>int]
     */
    public function upload(array $file): array {
        // 1. 錯誤碼檢查
        if ($file['error'] !== UPLOAD_ERR_OK) {
            return $this->fail($file['error'], $this->getUploadErrorMsg($file['error']));
        }

        // 2. 類型驗證(MIME + 副檢查擴展名)
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime  = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);
        if (!in_array($mime, $this->allowedMimes, true)) {
            return $this->fail(1001, "禁止的文件類型: $mime");
        }

        // 3. 大小驗證
        if ($file['size'] > $this->maxSize) {
            return $this->fail(1002, "文件過大,限制 {$this->maxSize} 字節");
        }

        // 4. 安全重命名:時間戳 + 隨機數 + 原擴展名
        $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        $ext = in_array($ext, ['jpg','jpeg','png','gif','webp']) ? $ext : 'jpg';
        $filename = date('YmdHis') . '_' . mt_rand(10000, 99999) . '.' . $ext;

        // 5. 日期分目錄:uploads/2024/01/15/
        $dateDir = date('Y/m/d');
        $saveDir = $this->saveRoot . '/' . $dateDir;
        if (!is_dir($saveDir) && !mkdir($saveDir, 0755, true)) {
            return $this->fail(1003, "無法創建目錄: $saveDir");
        }

        $savePath = $saveDir . '/' . $filename;
        $relativePath = $dateDir . '/' . $filename;
        $url = $this->urlPrefix . '/' . $relativePath;

        // 6. 移動文件
        if (!move_uploaded_file($file['tmp_name'], $savePath)) {
            return $this->fail(1004, "文件移動失敗");
        }

        // 7. 可選:二次驗證是否真實圖片
        if (@getimagesize($savePath) === false) {
            unlink($savePath);
            return $this->fail(1005, "非有效圖片文件");
        }

        return [
            'success' => true,
            'url'     => $url,
            'path'    => $savePath,
            'relative'=> $relativePath,
            'mime'    => $mime,
            'size'    => $file['size'],
        ];
    }

    private function fail(int $code, string $msg): array {
        return ['success' => false, 'error' => $msg, 'code' => $code];
    }

    private function getUploadErrorMsg(int $code): string {
        return match($code) {
            UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => '文件超過大小限制',
            UPLOAD_ERR_PARTIAL => '文件上傳不完整',
            UPLOAD_ERR_NO_FILE => '未選擇文件',
            UPLOAD_ERR_NO_TMP_DIR => '服務器臨時目錄缺失',
            UPLOAD_ERR_CANT_WRITE => '文件寫入失敗',
            UPLOAD_ERR_EXTENSION => 'PHP 擴展阻止上傳',
            default => '未知上傳錯誤',
        };
    }
}
?>

完整業務流程示例

<?php
// 1. 初始化上傳類
$uploader = new ImageUpload(
    saveRoot: __DIR__ . '/public/uploads',
    urlPrefix: '/uploads'
);

// 2. 處理上傳
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $result = $uploader->upload($_FILES['avatar'] ?? []);

    if ($result['success']) {
        // 3. 入庫(使用 PDO 預處理)
        $pdo = new PDO('mysql:host=localhost;dbname=app', 'user', 'pass');
        $stmt = $pdo->prepare('INSERT INTO user_avatars (user_id, url, path, mime, size) VALUES (?, ?, ?, ?, ?)');
        $stmt->execute([$_SESSION['user_id'], $result['url'], $result['relative'], $result['mime'], $result['size']]);

        echo json_encode(['code' => 0, 'msg' => '上傳成功', 'url' => $result['url']]);
    } else {
        http_response_code(400);
        echo json_encode(['code' => $result['code'], 'msg' => $result['error']]);
    }
    exit;
}
?>
<!DOCTYPE html>
<html>
<body>
<form method="post" enctype="multipart/form-data">
    <input type="file" name="avatar" accept="image/*" required>
    <button type="submit">上傳頭像</button>
</form>
</body>
</html>

安全加固清單

風險點防護措施
腳本偽裝成圖片finfo_file() 檢測真實 MIME + getimagesize() 二次驗證
文件名遍歷攻擊完全忽略原文件名,生成隨機名,路徑自行構造
上傳目錄可執行Nginx/Apache 配置 location ~ \.php$ { deny all; } 禁止執行
大文件填滿磁盤setMaxSize() + PHP upload_max_filesize/post_max_size 雙重限制
並發衝突時間戳+隨機數極低衝突率,極端可加 while(file_exists) rename

擴展建議

  • 多文件上傳:uploadMultiple(array $files): array 返回結果集
  • 圖片處理管道:集成 Intervention/Image 自動縮略、水印、WebP 轉換
  • 雲存儲適配:實現 StorageAdapter 接口,支持本地/OSS/S3 切換
  • 異步處理:上傳入隊列,Worker 處理壓縮/識別/審核,前端輪詢結果
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved