php实现图片上传校验并生成多尺寸缩略图

适用场景:电商、CMS、用户头像等图片管理场景。

1接收上传文件

2校验文件(类型、大小、是否真实图片等)

3生成多个尺寸的缩略图

4保存原图和缩略图到指定目录

<?php

class ImageUploader {
    private $uploadDir;
    private $allowedTypes;
    private $maxSize;
    private $thumbnailSizes;
    private $quality;

    public function __construct($config = []) {
        $this->uploadDir = isset($config['upload_dir']) ? rtrim($config['upload_dir'], '/') . '/' : './uploads/';
        $this->allowedTypes = $config['allowed_types'] ?? ['jpg', 'jpeg', 'png', 'gif'];
        $this->maxSize = $config['max_size'] ?? 5242880; // 5MB
        $this->thumbnailSizes = $config['thumbnail_sizes'] ?? [
            'small'  => ['width' => 150, 'height' => 150],
            'medium' => ['width' => 300, 'height' => 300],
            'large'  => ['width' => 600, 'height' => 600]
        ];
        $this->quality = $config['quality'] ?? 90;
    }

    public function upload($fileKey = 'image') {
        if (!isset($_FILES[$fileKey]) || empty($_FILES[$fileKey])) {
            return ['success' => false, 'message' => 'No file uploaded'];
        }

        $file = $_FILES[$fileKey];
        $errors = $this->validateFile($file);
        if (!empty($errors)) {
            return ['success' => false, 'message' => implode(', ', $errors)];
        }

        if (!$this->createDir($this->uploadDir)) {
            return ['success' => false, 'message' => 'Cannot create upload directory'];
        }

        $ext = $this->getFileExt($file['name']);
        $filename = date('YmdHis') . '_' . uniqid() . '.' . $ext;
        $originalPath = $this->uploadDir . $filename;

        if (!move_uploaded_file($file['tmp_name'], $originalPath)) {
            return ['success' => false, 'message' => 'Failed to save file'];
        }

        $thumbnails = [];
        foreach ($this->thumbnailSizes as $name => $size) {
            $thumbFile = pathinfo($filename, PATHINFO_FILENAME) . '_' . $name . '.' . $ext;
            $thumbPath = $this->uploadDir . $thumbFile;
            if (!$this->createThumbnail($originalPath, $thumbPath, $size['width'], $size['height'])) {
                return ['success' => false, 'message' => "Failed to create {$name} thumbnail"];
            }
            $thumbnails[$name] = $thumbPath;
        }

        return [
            'success' => true,
            'original' => $originalPath,
            'thumbnails' => $thumbnails
        ];
    }

    private function validateFile($file) {
        $errors = [];

        if ($file['error'] !== UPLOAD_ERR_OK) {
            $errors[] = 'Upload error: ' . $file['error'];
        }

        if ($file['size'] > $this->maxSize) {
            $errors[] = 'File too large';
        }

        $ext = $this->getFileExt($file['name']);
        if (!in_array($ext, $this->allowedTypes)) {
            $errors[] = 'Invalid file type';
        }

        $info = getimagesize($file['tmp_name']);
        if (!$info || !in_array($info[2], [IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_GIF])) {
            $errors[] = 'Not a valid image';
        }

        return $errors;
    }

    private function createThumbnail($src, $dest, $w, $h) {
        $info = getimagesize($src);
        if (!$info) return false;

        list($sw, $sh, $type) = $info;
        $ratio = min($w / $sw, $h / $sh);
        $nw = round($sw * $ratio);
        $nh = round($sh * $ratio);

        $srcImg = $this->createImageFromType($src, $type);
        if (!$srcImg) return false;

        $dstImg = imagecreatetruecolor($w, $h);
        $bg = imagecolorallocate($dstImg, 255, 255, 255);
        imagefill($dstImg, 0, 0, $bg);

        imagecopyresampled($dstImg, $srcImg, ($w-$nw)/2, ($h-$nh)/2, 0, 0, $nw, $nh, $sw, $sh);

        $ext = $this->getFileExt($dest);
        $result = $this->saveImageByType($dstImg, $dest, $ext);

        imagedestroy($srcImg);
        imagedestroy($dstImg);
        return $result;
    }

    private function createImageFromType($path, $type) {
        switch ($type) {
            case IMAGETYPE_JPEG: return imagecreatefromjpeg($path);
            case IMAGETYPE_PNG:  return imagecreatefrompng($path);
            case IMAGETYPE_GIF:  return imagecreatefromgif($path);
            default: return false;
        }
    }

    private function saveImageByType($img, $path, $ext) {
        switch (strtolower($ext)) {
            case 'jpg':
            case 'jpeg': return imagejpeg($img, $path, $this->quality);
            case 'png':  return imagepng($img, $path, (int)(9 - $this->quality / 10));
            case 'gif':  return imagegif($img, $path);
            default: return false;
        }
    }

    private function getFileExt($filename) {
        return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    }

    private function createDir($dir) {
        return is_dir($dir) || mkdir($dir, 0755, true);
    }
}

// ===== 调用示例 =====
/*
$config = [
    'upload_dir' => './uploads/',
    'max_size' => 10 * 1024 * 1024, // 10MB
    'thumbnail_sizes' => [
        'thumb' => ['width' => 100, 'height' => 100],
        'cover' => ['width' => 800, 'height' => 400]
    ]
];

$uploader = new ImageUploader($config);
$result = $uploader->upload('image'); // 'image' 是表单字段名

if ($result['success']) {
    echo "原图: " . $result['original'] . "\n";
    foreach ($result['thumbnails'] as $size => $path) {
        echo ucfirst($size) . ": " . $path . "\n";
    }
} else {
    echo "错误: " . $result['message'];
}
*/
?>