PHP 文件上传 完全指南

文件上传是Web开发中常见的功能,允许用户将文件从本地计算机传输到服务器。PHP提供了强大的文件上传支持,但同时也需要开发者注意安全性和性能问题。

核心概念:PHP通过$_FILES超全局数组处理上传的文件,使用move_uploaded_file()函数将临时文件移动到指定位置。

$_FILES 数组结构

当文件上传时,PHP会自动创建一个$_FILES数组,其中包含上传文件的所有信息:

数组元素 描述 示例值
$_FILES['file']['name'] 客户端文件的原始名称 photo.jpg
$_FILES['file']['type'] 文件的MIME类型(由浏览器提供,可能不可靠) image/jpeg
$_FILES['file']['size'] 文件大小(字节) 204800(200KB)
$_FILES['file']['tmp_name'] 服务器上的临时文件路径 /tmp/php/php4hst32
$_FILES['file']['error'] 错误代码(0表示成功) 0(无错误)
$_FILES['file']['full_path'] 文件的完整路径(PHP 8.1+) /home/user/photo.jpg

$_FILES['file']['error'] 错误代码

错误代码 常量 描述
0 UPLOAD_ERR_OK 文件上传成功
1 UPLOAD_ERR_INI_SIZE 文件大小超过了php.ini中的upload_max_filesize设置
2 UPLOAD_ERR_FORM_SIZE 文件大小超过了HTML表单中MAX_FILE_SIZE指定的值
3 UPLOAD_ERR_PARTIAL 文件只有部分被上传
4 UPLOAD_ERR_NO_FILE 没有文件被上传
6 UPLOAD_ERR_NO_TMP_DIR 找不到临时文件夹
7 UPLOAD_ERR_CANT_WRITE 文件写入失败
8 UPLOAD_ERR_EXTENSION PHP扩展阻止了文件上传

创建文件上传表单

1. 基本上传表单

<!DOCTYPE html>
<html>
<head>
    <title>文件上传示例</title>
    <style>
        .upload-form {
            max-width: 500px;
            margin: 50px auto;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
            background-color: #f9f9f9;
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input[type="file"] {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 3px;
        }
        button {
            background-color: #4CAF50;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 3px;
            cursor: pointer;
        }
        button:hover {
            background-color: #45a049;
        }
    </style>
</head>
<body>
    <div class="upload-form">
        <h2>上传文件</h2>
        <form action="upload.php" method="POST" enctype="multipart/form-data">
            <div class="form-group">
                <label for="file">选择文件:</label>
                <input type="file" name="file" id="file" required>
            </div>
            <div class="form-group">
                <label for="description">文件描述:</label>
                <input type="text" name="description" id="description" placeholder="可选">
            </div>
            <button type="submit" name="submit">上传文件</button>

            <!-- 隐藏字段限制文件大小(单位为字节) -->
            <input type="hidden" name="MAX_FILE_SIZE" value="10485760"> <!-- 10MB -->
        </form>

        <div class="upload-info">
            <p><strong>支持的文件类型:</strong> JPG, PNG, GIF, PDF, DOC, DOCX</p>
            <p><strong>最大文件大小:</strong> 10MB</p>
        </div>
    </div>
</body>
</html>

2. 多文件上传表单

<!DOCTYPE html>
<html>
<head>
    <title>多文件上传</title>
    <style>
        .upload-form {
            max-width: 600px;
            margin: 50px auto;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
            background-color: #f9f9f9;
        }
        .file-input-wrapper {
            margin-bottom: 15px;
        }
        .add-file-btn {
            background-color: #2196F3;
            color: white;
            padding: 8px 16px;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            margin-bottom: 15px;
        }
        .remove-file-btn {
            background-color: #f44336;
            color: white;
            padding: 5px 10px;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            margin-left: 10px;
        }
    </style>
    <script>
        let fileCount = 1;

        function addFileInput() {
            fileCount++;
            const container = document.getElementById('fileInputs');
            const div = document.createElement('div');
            div.className = 'file-input-wrapper';
            div.innerHTML = `
                <input type="file" name="files[]" required>
                <button type="button" class="remove-file-btn" onclick="removeFileInput(this)">删除</button>
            `;
            container.appendChild(div);
        }

        function removeFileInput(button) {
            if (fileCount > 1) {
                button.parentElement.remove();
                fileCount--;
            }
        }
    </script>
</head>
<body>
    <div class="upload-form">
        <h2>多文件上传</h2>
        <form action="multi_upload.php" method="POST" enctype="multipart/form-data">
            <div id="fileInputs">
                <div class="file-input-wrapper">
                    <input type="file" name="files[]" required>
                </div>
            </div>

            <button type="button" class="add-file-btn" onclick="addFileInput()">添加更多文件</button>
            <br><br>

            <button type="submit" name="submit">上传所有文件</button>

            <!-- 隐藏字段限制文件大小 -->
            <input type="hidden" name="MAX_FILE_SIZE" value="5242880"> <!-- 5MB -->
        </form>
    </div>
</body>
</html>

3. 使用AJAX进行文件上传

<!DOCTYPE html>
<html>
<head>
    <title>AJAX文件上传</title>
    <style>
        .upload-area {
            width: 400px;
            height: 200px;
            border: 2px dashed #ccc;
            border-radius: 10px;
            display: flex;
            align-items: center;
            justify-content: center;
            text-align: center;
            margin: 20px auto;
            cursor: pointer;
            transition: all 0.3s;
        }
        .upload-area:hover {
            border-color: #4CAF50;
            background-color: #f9f9f9;
        }
        .upload-area.dragover {
            border-color: #2196F3;
            background-color: #e3f2fd;
        }
        .progress-container {
            width: 400px;
            margin: 20px auto;
            display: none;
        }
        .progress-bar {
            width: 0%;
            height: 20px;
            background-color: #4CAF50;
            border-radius: 5px;
            transition: width 0.3s;
        }
        #preview {
            max-width: 200px;
            max-height: 200px;
            display: none;
            margin: 20px auto;
        }
    </style>
</head>
<body>
    <h2 style="text-align: center;">AJAX文件上传(支持拖放)</h2>

    <div class="upload-area" id="uploadArea">
        <div>
            <p><strong>点击选择文件或拖放到此处</strong></p>
            <p>支持 JPG, PNG, GIF 图片</p>
            <p>最大大小: 2MB</p>
        </div>
    </div>

    <input type="file" id="fileInput" accept="image/*" style="display: none;">

    <div class="progress-container" id="progressContainer">
        <p>上传进度: <span id="progressPercent">0%</span></p>
        <div class="progress-bar" id="progressBar"></div>
    </div>

    <img id="preview" src="" alt="预览">

    <div id="message" style="text-align: center; margin-top: 20px;"></div>

    <script>
        const uploadArea = document.getElementById('uploadArea');
        const fileInput = document.getElementById('fileInput');
        const preview = document.getElementById('preview');
        const progressContainer = document.getElementById('progressContainer');
        const progressBar = document.getElementById('progressBar');
        const progressPercent = document.getElementById('progressPercent');
        const messageDiv = document.getElementById('message');

        // 点击上传区域触发文件选择
        uploadArea.addEventListener('click', () => {
            fileInput.click();
        });

        // 文件选择变化
        fileInput.addEventListener('change', (e) => {
            if (e.target.files.length > 0) {
                handleFileUpload(e.target.files[0]);
            }
        });

        // 拖放功能
        uploadArea.addEventListener('dragover', (e) => {
            e.preventDefault();
            uploadArea.classList.add('dragover');
        });

        uploadArea.addEventListener('dragleave', () => {
            uploadArea.classList.remove('dragover');
        });

        uploadArea.addEventListener('drop', (e) => {
            e.preventDefault();
            uploadArea.classList.remove('dragover');

            if (e.dataTransfer.files.length > 0) {
                handleFileUpload(e.dataTransfer.files[0]);
            }
        });

        // 处理文件上传
        function handleFileUpload(file) {
            // 验证文件类型
            const validTypes = ['image/jpeg', 'image/png', 'image/gif'];
            if (!validTypes.includes(file.type)) {
                showMessage('错误:只支持 JPG, PNG, GIF 格式', 'error');
                return;
            }

            // 验证文件大小(2MB)
            if (file.size > 2 * 1024 * 1024) {
                showMessage('错误:文件大小不能超过2MB', 'error');
                return;
            }

            // 显示预览
            const reader = new FileReader();
            reader.onload = function(e) {
                preview.src = e.target.result;
                preview.style.display = 'block';
            }
            reader.readAsDataURL(file);

            // 上传文件
            uploadFile(file);
        }

        // 上传文件到服务器
        function uploadFile(file) {
            const formData = new FormData();
            formData.append('file', file);
            formData.append('ajax_upload', 'true');

            const xhr = new XMLHttpRequest();

            // 显示进度条
            progressContainer.style.display = 'block';
            progressBar.style.width = '0%';
            progressPercent.textContent = '0%';

            // 上传进度事件
            xhr.upload.addEventListener('progress', (e) => {
                if (e.lengthComputable) {
                    const percentComplete = (e.loaded / e.total) * 100;
                    progressBar.style.width = percentComplete + '%';
                    progressPercent.textContent = Math.round(percentComplete) + '%';
                }
            });

            // 上传完成事件
            xhr.addEventListener('load', () => {
                if (xhr.status === 200) {
                    const response = JSON.parse(xhr.responseText);
                    if (response.success) {
                        showMessage('文件上传成功!', 'success');
                    } else {
                        showMessage('上传失败:' + response.message, 'error');
                    }
                } else {
                    showMessage('上传失败,服务器错误', 'error');
                }

                // 隐藏进度条
                setTimeout(() => {
                    progressContainer.style.display = 'none';
                }, 2000);
            });

            // 上传错误事件
            xhr.addEventListener('error', () => {
                showMessage('上传失败,网络错误', 'error');
                progressContainer.style.display = 'none';
            });

            // 发送请求
            xhr.open('POST', 'upload.php');
            xhr.send(formData);
        }

        // 显示消息
        function showMessage(text, type) {
            messageDiv.textContent = text;
            messageDiv.style.color = type === 'error' ? '#f44336' : '#4CAF50';
            messageDiv.style.fontWeight = 'bold';
        }
    </script>
</body>
</html>

PHP文件上传处理

1. 基本文件上传处理

<?php
// upload.php - 基础文件上传处理

// 检查是否有文件上传
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
    $uploadDir = 'uploads/';

    // 确保上传目录存在
    if (!file_exists($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    $file = $_FILES['file'];
    $fileName = basename($file['name']);
    $fileTmpName = $file['tmp_name'];
    $fileSize = $file['size'];
    $fileError = $file['error'];

    // 检查上传是否成功
    if ($fileError === UPLOAD_ERR_OK) {
        // 生成安全的文件名(防止文件名攻击)
        $safeFileName = preg_replace('/[^a-zA-Z0-9._-]/', '_', $fileName);
        $safeFileName = time() . '_' . $safeFileName;

        $destination = $uploadDir . $safeFileName;

        // 移动临时文件到目标位置
        if (move_uploaded_file($fileTmpName, $destination)) {
            echo "<div style='padding: 20px; background-color: #d4edda; border: 1px solid #c3e6cb; border-radius: 5px;'>";
            echo "<h3>文件上传成功!</h3>";
            echo "<p><strong>文件名:</strong> " . htmlspecialchars($fileName) . "</p>";
            echo "<p><strong>保存为:</strong> " . htmlspecialchars($safeFileName) . "</p>";
            echo "<p><strong>文件大小:</strong> " . formatBytes($fileSize) . "</p>";
            echo "<p><strong>文件路径:</strong> " . htmlspecialchars($destination) . "</p>";

            // 如果是图片,显示缩略图
            if (strpos($file['type'], 'image/') === 0) {
                echo "<p><strong>图片预览:</strong></p>";
                echo "<img src='" . htmlspecialchars($destination) . "' alt='预览' style='max-width: 200px; max-height: 200px;'>";
            }

            echo "</div>";
        } else {
            echo "<div style='padding: 20px; background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 5px;'>";
            echo "<h3>文件上传失败!</h3>";
            echo "<p>无法将文件移动到目标位置。请检查目录权限。</p>";
            echo "</div>";
        }
    } else {
        echo "<div style='padding: 20px; background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 5px;'>";
        echo "<h3>文件上传失败!</h3>";
        echo "<p><strong>错误代码:</strong> " . $fileError . "</p>";
        echo "<p><strong>错误描述:</strong> " . getUploadError($fileError) . "</p>";
        echo "</div>";
    }
} else {
    echo "<div style='padding: 20px; background-color: #fff3cd; border: 1px solid #ffeaa7; border-radius: 5px;'>";
    echo "<h3>没有文件被上传</h3>";
    echo "<p>请通过表单上传文件。</p>";
    echo "</div>";
}

/**
 * 格式化字节大小
 */
function formatBytes($bytes, $precision = 2) {
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);
    $bytes /= pow(1024, $pow);
    return round($bytes, $precision) . ' ' . $units[$pow];
}

/**
 * 获取上传错误描述
 */
function getUploadError($errorCode) {
    switch ($errorCode) {
        case UPLOAD_ERR_INI_SIZE:
            return '文件大小超过了服务器限制。';
        case UPLOAD_ERR_FORM_SIZE:
            return '文件大小超过了表单限制。';
        case UPLOAD_ERR_PARTIAL:
            return '文件只有部分被上传。';
        case UPLOAD_ERR_NO_FILE:
            return '没有文件被上传。';
        case UPLOAD_ERR_NO_TMP_DIR:
            return '找不到临时文件夹。';
        case UPLOAD_ERR_CANT_WRITE:
            return '文件写入失败。';
        case UPLOAD_ERR_EXTENSION:
            return 'PHP扩展阻止了文件上传。';
        default:
            return '未知错误。';
    }
}
?>

2. 安全的文件上传类

<?php
/**
 * 安全的文件上传处理类
 */
class SecureFileUploader {
    private $allowedExtensions = [];
    private $allowedMimeTypes = [];
    private $maxFileSize = 0;
    private $uploadDir = '';
    private $errors = [];

    public function __construct($uploadDir = 'uploads/') {
        $this->uploadDir = rtrim($uploadDir, '/') . '/';

        // 创建上传目录(如果不存在)
        if (!file_exists($this->uploadDir)) {
            if (!mkdir($this->uploadDir, 0755, true)) {
                $this->addError('无法创建上传目录');
            }
        }

        // 检查目录是否可写
        if (!is_writable($this->uploadDir)) {
            $this->addError('上传目录不可写');
        }
    }

    /**
     * 设置允许的文件扩展名
     */
    public function setAllowedExtensions($extensions) {
        $this->allowedExtensions = array_map('strtolower', $extensions);
        return $this;
    }

    /**
     * 设置允许的MIME类型
     */
    public function setAllowedMimeTypes($mimeTypes) {
        $this->allowedMimeTypes = $mimeTypes;
        return $this;
    }

    /**
     * 设置最大文件大小(字节)
     */
    public function setMaxFileSize($sizeInBytes) {
        $this->maxFileSize = $sizeInBytes;
        return $this;
    }

    /**
     * 处理单个文件上传
     */
    public function upload($fileInputName, $customName = null) {
        $this->errors = [];

        // 检查是否有文件上传
        if (!isset($_FILES[$fileInputName]) || $_FILES[$fileInputName]['error'] === UPLOAD_ERR_NO_FILE) {
            $this->addError('没有文件被上传');
            return false;
        }

        $file = $_FILES[$fileInputName];

        // 检查上传错误
        if ($file['error'] !== UPLOAD_ERR_OK) {
            $this->addError($this->getUploadErrorMessage($file['error']));
            return false;
        }

        // 验证文件大小
        if ($this->maxFileSize > 0 && $file['size'] > $this->maxFileSize) {
            $this->addError('文件大小超过限制');
            return false;
        }

        // 获取文件信息
        $originalName = $file['name'];
        $tmpName = $file['tmp_name'];
        $fileSize = $file['size'];

        // 获取文件扩展名
        $fileExtension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));

        // 验证文件扩展名
        if (!empty($this->allowedExtensions) && !in_array($fileExtension, $this->allowedExtensions)) {
            $this->addError('不支持的文件类型');
            return false;
        }

        // 验证MIME类型(使用finfo进行更可靠的检测)
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $detectedMimeType = finfo_file($finfo, $tmpName);
        finfo_close($finfo);

        if (!empty($this->allowedMimeTypes) && !in_array($detectedMimeType, $this->allowedMimeTypes)) {
            $this->addError('不支持的文件格式');
            return false;
        }

        // 生成安全的文件名
        if ($customName) {
            $safeFileName = $this->sanitizeFileName($customName . '.' . $fileExtension);
        } else {
            $safeFileName = $this->generateSafeFileName($originalName);
        }

        // 检查文件名是否已存在(避免覆盖)
        $destination = $this->uploadDir . $safeFileName;
        $counter = 1;
        while (file_exists($destination)) {
            $safeFileName = $this->generateSafeFileName($originalName, $counter);
            $destination = $this->uploadDir . $safeFileName;
            $counter++;
        }

        // 移动文件
        if (move_uploaded_file($tmpName, $destination)) {
            return [
                'success' => true,
                'original_name' => $originalName,
                'saved_name' => $safeFileName,
                'file_path' => $destination,
                'file_size' => $fileSize,
                'file_extension' => $fileExtension,
                'mime_type' => $detectedMimeType
            ];
        } else {
            $this->addError('文件移动失败');
            return false;
        }
    }

    /**
     * 处理多文件上传
     */
    public function uploadMultiple($fileInputName) {
        $results = [];

        // 检查是否是多文件上传
        if (!isset($_FILES[$fileInputName]['name']) || !is_array($_FILES[$fileInputName]['name'])) {
            $this->addError('不是多文件上传');
            return false;
        }

        $files = $_FILES[$fileInputName];
        $fileCount = count($files['name']);

        for ($i = 0; $i < $fileCount; $i++) {
            // 如果没有选择文件,跳过
            if ($files['error'][$i] === UPLOAD_ERR_NO_FILE) {
                continue;
            }

            // 创建临时文件数组
            $file = [
                'name' => $files['name'][$i],
                'type' => $files['type'][$i],
                'tmp_name' => $files['tmp_name'][$i],
                'error' => $files['error'][$i],
                'size' => $files['size'][$i]
            ];

            // 保存到临时$_FILES数组以便重用upload方法
            $_FILES['temp_file'] = $file;

            // 使用upload方法处理单个文件
            $result = $this->upload('temp_file');

            if ($result) {
                $results[] = $result;
            }

            // 清理临时文件数组
            unset($_FILES['temp_file']);
        }

        return $results;
    }

    /**
     * 生成安全的文件名
     */
    private function generateSafeFileName($originalName, $counter = null) {
        $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
        $nameWithoutExt = pathinfo($originalName, PATHINFO_FILENAME);

        // 清理文件名
        $safeName = $this->sanitizeFileName($nameWithoutExt);

        // 添加时间戳和随机字符串
        $timestamp = time();
        $random = bin2hex(random_bytes(4));

        if ($counter) {
            $fileName = "{$timestamp}_{$random}_{$counter}.{$extension}";
        } else {
            $fileName = "{$timestamp}_{$random}.{$extension}";
        }

        return $fileName;
    }

    /**
     * 清理文件名(移除特殊字符)
     */
    private function sanitizeFileName($fileName) {
        // 移除路径信息
        $fileName = basename($fileName);

        // 替换空格为下划线
        $fileName = str_replace(' ', '_', $fileName);

        // 只保留字母、数字、下划线、连字符和点
        $fileName = preg_replace('/[^a-zA-Z0-9._-]/', '', $fileName);

        // 移除多个连续的点
        $fileName = preg_replace('/\.{2,}/', '.', $fileName);

        return $fileName;
    }

    /**
     * 获取上传错误信息
     */
    private function getUploadErrorMessage($errorCode) {
        $messages = [
            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扩展阻止了文件上传'
        ];

        return $messages[$errorCode] ?? '未知上传错误';
    }

    /**
     * 添加错误信息
     */
    private function addError($message) {
        $this->errors[] = $message;
    }

    /**
     * 获取所有错误信息
     */
    public function getErrors() {
        return $this->errors;
    }

    /**
     * 获取最后一个错误信息
     */
    public function getLastError() {
        return end($this->errors) ?: '';
    }

    /**
     * 是否有错误
     */
    public function hasErrors() {
        return !empty($this->errors);
    }
}

// 使用示例
$uploader = new SecureFileUploader('uploads/');

// 配置上传设置
$uploader
    ->setAllowedExtensions(['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'])
    ->setAllowedMimeTypes([
        'image/jpeg',
        'image/png',
        'image/gif',
        'application/pdf',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    ])
    ->setMaxFileSize(5 * 1024 * 1024); // 5MB

// 处理单个文件上传
if (isset($_FILES['single_file'])) {
    $result = $uploader->upload('single_file');

    if ($result) {
        echo "<div style='color: green;'>文件上传成功!</div>";
        echo "<pre>" . print_r($result, true) . "</pre>";
    } else {
        echo "<div style='color: red;'>文件上传失败!</div>";
        foreach ($uploader->getErrors() as $error) {
            echo "<p>错误:$error</p>";
        }
    }
}

// 处理多文件上传
if (isset($_FILES['multiple_files'])) {
    $results = $uploader->uploadMultiple('multiple_files');

    if ($results) {
        echo "<div style='color: green;'>上传了 " . count($results) . " 个文件</div>";
        foreach ($results as $result) {
            echo "<p>{$result['original_name']} -> {$result['saved_name']}</p>";
        }
    } else {
        echo "<div style='color: red;'>多文件上传失败!</div>";
        foreach ($uploader->getErrors() as $error) {
            echo "<p>错误:$error</p>";
        }
    }
}
?>

高级文件验证与处理

1. 图片文件验证与处理

<?php
/**
 * 图片上传处理类
 */
class ImageUploader {
    private $maxWidth = 1920;
    private $maxHeight = 1080;
    private $quality = 85;
    private $createThumbnail = true;
    private $thumbnailWidth = 200;
    private $thumbnailHeight = 200;

    public function uploadImage($fileInputName) {
        // 验证是否是图片文件
        if (!isset($_FILES[$fileInputName])) {
            return ['success' => false, 'message' => '没有文件上传'];
        }

        $file = $_FILES[$fileInputName];

        // 检查上传错误
        if ($file['error'] !== UPLOAD_ERR_OK) {
            return ['success' => false, 'message' => '上传错误:' . $file['error']];
        }

        // 验证文件类型
        $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
        $detectedType = mime_content_type($file['tmp_name']);

        if (!in_array($detectedType, $allowedTypes)) {
            return ['success' => false, 'message' => '不支持的文件类型'];
        }

        // 验证文件扩展名
        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];

        if (!in_array($extension, $allowedExtensions)) {
            return ['success' => false, 'message' => '不支持的文件格式'];
        }

        // 获取图片信息
        $imageInfo = @getimagesize($file['tmp_name']);
        if (!$imageInfo) {
            return ['success' => false, 'message' => '不是有效的图片文件'];
        }

        list($width, $height, $type) = $imageInfo;

        // 检查图片尺寸
        if ($width > $this->maxWidth || $height > $this->maxHeight) {
            return ['success' => false, 'message' => "图片尺寸过大(最大 {$this->maxWidth}x{$this->maxHeight})"];
        }

        // 创建上传目录
        $uploadDir = 'uploads/images/';
        $thumbDir = $uploadDir . 'thumbs/';

        if (!file_exists($uploadDir)) mkdir($uploadDir, 0755, true);
        if ($this->createThumbnail && !file_exists($thumbDir)) mkdir($thumbDir, 0755, true);

        // 生成安全的文件名
        $timestamp = time();
        $random = bin2hex(random_bytes(8));
        $safeFileName = "{$timestamp}_{$random}.{$extension}";

        $destination = $uploadDir . $safeFileName;

        // 移动文件
        if (!move_uploaded_file($file['tmp_name'], $destination)) {
            return ['success' => false, 'message' => '文件移动失败'];
        }

        $result = [
            'success' => true,
            'original_name' => $file['name'],
            'saved_name' => $safeFileName,
            'file_path' => $destination,
            'width' => $width,
            'height' => $height,
            'mime_type' => $detectedType,
            'thumbnail' => null
        ];

        // 创建缩略图
        if ($this->createThumbnail) {
            $thumbnailPath = $this->createThumbnail($destination, $thumbDir . $safeFileName);
            if ($thumbnailPath) {
                $result['thumbnail'] = $thumbnailPath;
            }
        }

        return $result;
    }

    /**
     * 创建缩略图
     */
    private function createThumbnail($sourcePath, $destinationPath) {
        $imageInfo = getimagesize($sourcePath);
        if (!$imageInfo) return false;

        list($width, $height, $type) = $imageInfo;

        // 计算缩略图尺寸(保持纵横比)
        $srcRatio = $width / $height;
        $dstRatio = $this->thumbnailWidth / $this->thumbnailHeight;

        if ($srcRatio > $dstRatio) {
            // 源图片更宽
            $newWidth = $this->thumbnailWidth;
            $newHeight = $this->thumbnailWidth / $srcRatio;
        } else {
            // 源图片更高或相等
            $newHeight = $this->thumbnailHeight;
            $newWidth = $this->thumbnailHeight * $srcRatio;
        }

        // 创建源图片资源
        switch ($type) {
            case IMAGETYPE_JPEG:
                $source = imagecreatefromjpeg($sourcePath);
                break;
            case IMAGETYPE_PNG:
                $source = imagecreatefrompng($sourcePath);
                break;
            case IMAGETYPE_GIF:
                $source = imagecreatefromgif($sourcePath);
                break;
            case IMAGETYPE_WEBP:
                $source = imagecreatefromwebp($sourcePath);
                break;
            default:
                return false;
        }

        if (!$source) return false;

        // 创建缩略图画布
        $thumbnail = imagecreatetruecolor($this->thumbnailWidth, $this->thumbnailHeight);

        // 设置透明背景(针对PNG和GIF)
        if ($type === IMAGETYPE_PNG || $type === IMAGETYPE_GIF) {
            imagealphablending($thumbnail, false);
            imagesavealpha($thumbnail, true);
            $transparent = imagecolorallocatealpha($thumbnail, 0, 0, 0, 127);
            imagefill($thumbnail, 0, 0, $transparent);
        }

        // 计算居中位置
        $dstX = ($this->thumbnailWidth - $newWidth) / 2;
        $dstY = ($this->thumbnailHeight - $newHeight) / 2;

        // 调整大小并居中
        imagecopyresampled(
            $thumbnail, $source,
            $dstX, $dstY, 0, 0,
            $newWidth, $newHeight, $width, $height
        );

        // 保存缩略图
        switch ($type) {
            case IMAGETYPE_JPEG:
                imagejpeg($thumbnail, $destinationPath, $this->quality);
                break;
            case IMAGETYPE_PNG:
                imagepng($thumbnail, $destinationPath);
                break;
            case IMAGETYPE_GIF:
                imagegif($thumbnail, $destinationPath);
                break;
            case IMAGETYPE_WEBP:
                imagewebp($thumbnail, $destinationPath, $this->quality);
                break;
        }

        // 释放内存
        imagedestroy($source);
        imagedestroy($thumbnail);

        return $destinationPath;
    }

    // 设置方法
    public function setMaxDimensions($width, $height) {
        $this->maxWidth = $width;
        $this->maxHeight = $height;
        return $this;
    }

    public function setQuality($quality) {
        $this->quality = max(0, min(100, $quality));
        return $this;
    }

    public function setCreateThumbnail($create) {
        $this->createThumbnail = $create;
        return $this;
    }

    public function setThumbnailDimensions($width, $height) {
        $this->thumbnailWidth = $width;
        $this->thumbnailHeight = $height;
        return $this;
    }
}

// 使用示例
$imageUploader = new ImageUploader();
$imageUploader
    ->setMaxDimensions(4000, 4000)
    ->setQuality(90)
    ->setThumbnailDimensions(300, 300);

if (isset($_FILES['image'])) {
    $result = $imageUploader->uploadImage('image');

    if ($result['success']) {
        echo "<div style='border: 2px solid green; padding: 20px; margin: 20px;'>";
        echo "<h3>图片上传成功!</h3>";
        echo "<p><strong>原始名称:</strong> {$result['original_name']}</p>";
        echo "<p><strong>保存名称:</strong> {$result['saved_name']}</p>";
        echo "<p><strong>图片尺寸:</strong> {$result['width']} x {$result['height']}</p>";

        // 显示原图
        echo "<p><strong>原图预览:</strong></p>";
        echo "<img src='{$result['file_path']}' style='max-width: 300px; max-height: 300px;'>";

        // 显示缩略图
        if ($result['thumbnail']) {
            echo "<p><strong>缩略图:</strong></p>";
            echo "<img src='{$result['thumbnail']}'>";
        }

        echo "</div>";
    } else {
        echo "<div style='border: 2px solid red; padding: 20px; margin: 20px;'>";
        echo "<h3>图片上传失败!</h3>";
        echo "<p>错误信息:{$result['message']}</p>";
        echo "</div>";
    }
}
?>

2. PDF文件验证与处理

<?php
/**
 * PDF文件上传处理类
 */
class PdfUploader {
    private $maxPages = 50;
    private $maxSize = 10 * 1024 * 1024; // 10MB
    private $scanForViruses = false; // 在实际应用中应该启用

    public function uploadPdf($fileInputName) {
        // 验证PDF文件
        if (!isset($_FILES[$fileInputName])) {
            return ['success' => false, 'message' => '没有文件上传'];
        }

        $file = $_FILES[$fileInputName];

        // 检查上传错误
        if ($file['error'] !== UPLOAD_ERR_OK) {
            return ['success' => false, 'message' => '上传错误:' . $file['error']];
        }

        // 验证文件大小
        if ($file['size'] > $this->maxSize) {
            return ['success' => false, 'message' => '文件大小超过限制'];
        }

        // 验证文件类型
        $allowedTypes = ['application/pdf', 'application/x-pdf'];
        $detectedType = mime_content_type($file['tmp_name']);

        if (!in_array($detectedType, $allowedTypes)) {
            return ['success' => false, 'message' => '只支持PDF文件'];
        }

        // 验证文件扩展名
        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if ($extension !== 'pdf') {
            return ['success' => false, 'message' => '文件扩展名必须是.pdf'];
        }

        // 检查PDF文件结构(基本验证)
        if (!$this->isValidPdf($file['tmp_name'])) {
            return ['success' => false, 'message' => '不是有效的PDF文件'];
        }

        // 检查PDF页数
        $pageCount = $this->getPdfPageCount($file['tmp_name']);
        if ($pageCount > $this->maxPages) {
            return ['success' => false, 'message' => "PDF页数超过限制(最多{$this->maxPages}页)"];
        }

        // 创建上传目录
        $uploadDir = 'uploads/documents/';
        if (!file_exists($uploadDir)) {
            mkdir($uploadDir, 0755, true);
        }

        // 生成安全的文件名
        $timestamp = time();
        $random = bin2hex(random_bytes(8));
        $safeFileName = "{$timestamp}_{$random}.pdf";

        $destination = $uploadDir . $safeFileName;

        // 移动文件
        if (!move_uploaded_file($file['tmp_name'], $destination)) {
            return ['success' => false, 'message' => '文件移动失败'];
        }

        return [
            'success' => true,
            'original_name' => $file['name'],
            'saved_name' => $safeFileName,
            'file_path' => $destination,
            'file_size' => $file['size'],
            'page_count' => $pageCount,
            'upload_time' => date('Y-m-d H:i:s')
        ];
    }

    /**
     * 验证PDF文件
     */
    private function isValidPdf($filePath) {
        // 检查文件是否以PDF标记开头
        $handle = fopen($filePath, 'r');
        $header = fread($handle, 5);
        fclose($handle);

        return $header === '%PDF-';
    }

    /**
     * 获取PDF页数
     */
    private function getPdfPageCount($filePath) {
        // 简单的方法:使用正则表达式匹配页面标记
        $content = file_get_contents($filePath);
        $pattern = '/\/Type\s*\/Page[^s]/';
        preg_match_all($pattern, $content, $matches);

        return count($matches[0]);
    }

    /**
     * 提取PDF文本内容(需要安装pdftotext工具)
     */
    public function extractPdfText($filePath) {
        if (!function_exists('shell_exec')) {
            return false;
        }

        // 检查是否安装了pdftotext
        $output = shell_exec('which pdftotext 2>&1');
        if (empty($output)) {
            return false;
        }

        $tempFile = tempnam(sys_get_temp_dir(), 'pdf_text_');
        $command = "pdftotext '{$filePath}' '{$tempFile}' 2>&1";
        shell_exec($command);

        if (file_exists($tempFile)) {
            $text = file_get_contents($tempFile);
            unlink($tempFile);
            return $text;
        }

        return false;
    }

    // 设置方法
    public function setMaxPages($pages) {
        $this->maxPages = $pages;
        return $this;
    }

    public function setMaxSize($sizeInBytes) {
        $this->maxSize = $sizeInBytes;
        return $this;
    }
}

// 使用示例
$pdfUploader = new PdfUploader();
$pdfUploader
    ->setMaxPages(100)
    ->setMaxSize(20 * 1024 * 1024); // 20MB

if (isset($_FILES['pdf_document'])) {
    $result = $pdfUploader->uploadPdf('pdf_document');

    if ($result['success']) {
        echo "<div style='border: 2px solid green; padding: 20px; margin: 20px;'>";
        echo "<h3>PDF文件上传成功!</h3>";
        echo "<p><strong>文件名称:</strong> {$result['original_name']}</p>";
        echo "<p><strong>保存名称:</strong> {$result['saved_name']}</p>";
        echo "<p><strong>文件大小:</strong> " . formatBytes($result['file_size']) . "</p>";
        echo "<p><strong>页数:</strong> {$result['page_count']}</p>";
        echo "<p><strong>上传时间:</strong> {$result['upload_time']}</p>";

        // 提取文本内容
        $text = $pdfUploader->extractPdfText($result['file_path']);
        if ($text) {
            echo "<p><strong>文本预览(前500字符):</strong></p>";
            echo "<div style='border: 1px solid #ddd; padding: 10px; max-height: 200px; overflow-y: auto;'>";
            echo htmlspecialchars(substr($text, 0, 500)) . "...";
            echo "</div>";
        }

        echo "</div>";
    } else {
        echo "<div style='border: 2px solid red; padding: 20px; margin: 20px;'>";
        echo "<h3>PDF文件上传失败!</h3>";
        echo "<p>错误信息:{$result['message']}</p>";
        echo "</div>";
    }
}

function formatBytes($bytes, $precision = 2) {
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);
    $bytes /= pow(1024, $pow);
    return round($bytes, $precision) . ' ' . $units[$pow];
}
?>

PHP配置文件设置

1. php.ini中与文件上传相关的重要设置

配置项 默认值 推荐值 描述
file_uploads On On 是否允许HTTP文件上传
upload_max_filesize 2M 10M 或更大 允许上传的文件最大尺寸
post_max_size 8M 12M(比upload_max_filesize大) POST数据最大尺寸(必须大于upload_max_filesize)
max_file_uploads 20 50 一次请求中最多上传的文件数
upload_tmp_dir 系统默认 自定义目录 上传临时文件的目录
max_input_time 60 300 脚本接收输入数据的最大时间(秒)
max_execution_time 30 300 脚本最大执行时间(秒)
memory_limit 128M 256M 或更大 脚本内存限制

2. 运行时配置修改

<?php
/**
 * 在运行时调整文件上传配置
 */
class UploadConfig {
    public static function adjustForLargeFiles() {
        // 增加上传文件大小限制
        ini_set('upload_max_filesize', '50M');
        ini_set('post_max_size', '55M');

        // 增加脚本执行时间(大文件需要更长时间)
        ini_set('max_execution_time', '600'); // 10分钟
        ini_set('max_input_time', '600');

        // 增加内存限制
        ini_set('memory_limit', '512M');

        // 设置自定义临时目录(确保可写)
        $tempDir = sys_get_temp_dir() . '/custom_uploads/';
        if (!file_exists($tempDir)) {
            mkdir($tempDir, 0755, true);
        }
        ini_set('upload_tmp_dir', $tempDir);
    }

    public static function getCurrentConfig() {
        return [
            'file_uploads' => ini_get('file_uploads'),
            'upload_max_filesize' => ini_get('upload_max_filesize'),
            'post_max_size' => ini_get('post_max_size'),
            'max_file_uploads' => ini_get('max_file_uploads'),
            'upload_tmp_dir' => ini_get('upload_tmp_dir'),
            'max_execution_time' => ini_get('max_execution_time'),
            'max_input_time' => ini_get('max_input_time'),
            'memory_limit' => ini_get('memory_limit')
        ];
    }

    public static function checkRequirements() {
        $errors = [];
        $config = self::getCurrentConfig();

        // 检查文件上传是否启用
        if ($config['file_uploads'] !== '1') {
            $errors[] = '文件上传功能被禁用';
        }

        // 检查临时目录是否可写
        $tempDir = $config['upload_tmp_dir'] ?: sys_get_temp_dir();
        if (!is_writable($tempDir)) {
            $errors[] = "临时目录不可写:{$tempDir}";
        }

        // 检查上传大小限制
        $uploadMax = self::convertToBytes($config['upload_max_filesize']);
        $postMax = self::convertToBytes($config['post_max_size']);

        if ($postMax < $uploadMax) {
            $errors[] = "post_max_size 必须大于 upload_max_filesize";
        }

        return [
            'config' => $config,
            'errors' => $errors,
            'passed' => empty($errors)
        ];
    }

    /**
     * 将配置文件中的大小字符串转换为字节
     */
    private static function convertToBytes($sizeStr) {
        $unit = strtoupper(substr($sizeStr, -1));
        $size = (int)substr($sizeStr, 0, -1);

        switch ($unit) {
            case 'G': $size *= 1024;
            case 'M': $size *= 1024;
            case 'K': $size *= 1024;
        }

        return $size;
    }
}

// 使用示例
echo "<h3>当前上传配置:</h3>";
$currentConfig = UploadConfig::getCurrentConfig();
foreach ($currentConfig as $key => $value) {
    echo "<p><strong>$key:</strong> $value</p>";
}

echo "<h3>配置检查:</h3>";
$check = UploadConfig::checkRequirements();
if ($check['passed']) {
    echo "<div style='color: green;'>✓ 所有配置检查通过</div>";
} else {
    echo "<div style='color: red;'>✗ 发现配置问题:</div>";
    foreach ($check['errors'] as $error) {
        echo "<p>• $error</p>";
    }
}

// 为大文件上传调整配置
UploadConfig::adjustForLargeFiles();
echo "<h3>已为大文件上传调整配置</h3>";
?>

文件上传安全最佳实践

安全建议:

  1. 验证文件类型:不要依赖客户端提供的MIME类型,使用服务器端检测(如finfo_file()getimagesize()
  2. 限制文件扩展名:只允许特定的文件扩展名
  3. 检查文件内容:对于图片,使用GD库或ImageMagick验证是否为有效图片
  4. 重命名文件:使用随机生成的文件名,避免用户控制文件名
  5. 设置文件大小限制:在服务器端和客户端都设置合理的大小限制
  6. 存储目录安全:上传目录应放在Web根目录之外,或通过.htaccess限制直接访问
  7. 禁用PHP执行:确保上传目录不能执行PHP脚本
  8. 使用病毒扫描:对于生产环境,考虑集成病毒扫描功能
  9. 日志记录:记录所有文件上传操作,包括成功和失败
  10. CSRF保护:为上传表单添加CSRF令牌保护

.htaccess文件保护示例

# 禁止上传目录执行PHP
<FilesMatch "\.(php|php5|phtml)$">
    Order Allow,Deny
    Deny from all
</FilesMatch>

# 限制直接访问某些文件类型
<FilesMatch "\.(htaccess|htpasswd|ini|log|sh|sql)$">
    Order Allow,Deny
    Deny from all
</FilesMatch>

# 禁用目录浏览
Options -Indexes

# 设置文件缓存头
<FilesMatch "\.(jpg|jpeg|png|gif|pdf)$">
    Header set Cache-Control "max-age=2592000, public"
</FilesMatch>

# 限制文件大小(服务器端)
php_value upload_max_filesize 10M
php_value post_max_size 12M

# 添加安全头
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-XSS-Protection "1; mode=block"

CSRF保护的AJAX上传示例

<?php
// 生成CSRF令牌
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// 处理AJAX上传请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ajax_upload'])) {
    // 验证CSRF令牌
    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
        http_response_code(403);
        echo json_encode(['success' => false, 'message' => 'CSRF令牌验证失败']);
        exit;
    }

    // 验证文件上传
    if (!isset($_FILES['file'])) {
        echo json_encode(['success' => false, 'message' => '没有文件被上传']);
        exit;
    }

    $file = $_FILES['file'];

    // 安全文件上传处理
    $uploader = new SecureFileUploader('uploads/');
    $uploader
        ->setAllowedExtensions(['jpg', 'jpeg', 'png', 'gif'])
        ->setAllowedMimeTypes(['image/jpeg', 'image/png', 'image/gif'])
        ->setMaxFileSize(2 * 1024 * 1024);

    $result = $uploader->upload('file');

    if ($result) {
        echo json_encode([
            'success' => true,
            'message' => '文件上传成功',
            'file_name' => $result['original_name'],
            'file_url' => $result['file_path'],
            'file_size' => $result['file_size']
        ]);
    } else {
        echo json_encode([
            'success' => false,
            'message' => '文件上传失败',
            'errors' => $uploader->getErrors()
        ]);
    }
    exit;
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>安全的AJAX文件上传</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <form id="uploadForm" enctype="multipart/form-data">
        <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
        <input type="hidden" name="ajax_upload" value="1">

        <input type="file" name="file" id="fileInput" required>
        <button type="submit">上传文件</button>
    </form>

    <div id="result"></div>

    <script>
        $(document).ready(function() {
            $('#uploadForm').on('submit', function(e) {
                e.preventDefault();

                var formData = new FormData(this);

                $.ajax({
                    url: 'upload.php',
                    type: 'POST',
                    data: formData,
                    processData: false,
                    contentType: false,
                    dataType: 'json',
                    success: function(response) {
                        if (response.success) {
                            $('#result').html('<div style="color: green;">' + response.message + '</div>');
                            $('#result').append('<p>文件名:' + response.file_name + '</p>');
                            $('#result').append('<p>文件大小:' + (response.file_size / 1024).toFixed(2) + ' KB</p>');
                        } else {
                            $('#result').html('<div style="color: red;">' + response.message + '</div>');
                            if (response.errors) {
                                $('#result').append('<ul>');
                                response.errors.forEach(function(error) {
                                    $('#result').append('<li>' + error + '</li>');
                                });
                                $('#result').append('</ul>');
                            }
                        }
                    },
                    error: function() {
                        $('#result').html('<div style="color: red;">上传失败,请重试</div>');
                    }
                });
            });
        });
    </script>
</body>
</html>

常见问题解答

1. 如何解决"文件大小超过限制"错误?

需要调整以下配置:

  1. 修改php.ini
    upload_max_filesize = 10M
    post_max_size = 12M
  2. 通过.htaccess修改(如果使用Apache)
    php_value upload_max_filesize 10M
    php_value post_max_size 12M
  3. 在PHP脚本中动态修改
    ini_set('upload_max_filesize', '10M');
    ini_set('post_max_size', '12M');
注意:post_max_size必须大于upload_max_filesize,因为POST数据包含文件和其他表单数据。

2. 如何防止恶意文件上传?

多层次防御策略:

  • 服务器端验证:使用finfo_file()检测真实MIME类型
  • 文件内容检查:对于图片,使用getimagesize()验证
  • 重命名文件:使用随机生成的文件名,避免执行恶意脚本
  • 目录权限:上传目录禁止执行PHP脚本
  • 文件扩展名白名单:只允许特定的文件扩展名
  • 文件头检查:验证文件前几个字节的魔术数字(magic number)

3. 如何实现大文件分片上传?

<?php
// 大文件分片上传示例
class ChunkedFileUpload {
    private $tempDir = 'uploads/temp/';
    private $finalDir = 'uploads/final/';

    public function __construct() {
        if (!file_exists($this->tempDir)) mkdir($this->tempDir, 0755, true);
        if (!file_exists($this->finalDir)) mkdir($this->finalDir, 0755, true);
    }

    public function uploadChunk($fileInputName, $chunkNumber, $totalChunks, $fileName) {
        if (!isset($_FILES[$fileInputName])) {
            return ['success' => false, 'message' => '没有文件被上传'];
        }

        $file = $_FILES[$fileInputName];
        $tempFilePath = $this->tempDir . $fileName . '.part' . $chunkNumber;

        // 保存分片
        if (!move_uploaded_file($file['tmp_name'], $tempFilePath)) {
            return ['success' => false, 'message' => '分片保存失败'];
        }

        // 检查是否所有分片都已上传
        $uploadedChunks = 0;
        for ($i = 1; $i <= $totalChunks; $i++) {
            if (file_exists($this->tempDir . $fileName . '.part' . $i)) {
                $uploadedChunks++;
            }
        }

        // 如果所有分片都已上传,合并文件
        if ($uploadedChunks == $totalChunks) {
            $finalFilePath = $this->finalDir . $fileName;
            $finalFile = fopen($finalFilePath, 'wb');

            for ($i = 1; $i <= $totalChunks; $i++) {
                $chunkFile = $this->tempDir . $fileName . '.part' . $i;
                $chunkContent = file_get_contents($chunkFile);
                fwrite($finalFile, $chunkContent);
                unlink($chunkFile); // 删除分片文件
            }

            fclose($finalFile);

            return [
                'success' => true,
                'message' => '文件上传完成',
                'file_path' => $finalFilePath,
                'file_size' => filesize($finalFilePath)
            ];
        }

        return [
            'success' => true,
            'message' => "分片 {$chunkNumber}/{$totalChunks} 上传成功",
            'progress' => round(($uploadedChunks / $totalChunks) * 100, 2)
        ];
    }
}

// 使用示例
if (isset($_POST['chunked_upload'])) {
    $uploader = new ChunkedFileUpload();

    $result = $uploader->uploadChunk(
        $_POST['file_input'] ?? 'file',
        $_POST['chunk_number'] ?? 1,
        $_POST['total_chunks'] ?? 1,
        $_POST['file_name'] ?? 'uploaded_file'
    );

    echo json_encode($result);
}
?>

4. 如何提高文件上传的性能?

性能优化建议:

  • 使用CDN:将上传的文件分发到CDN网络
  • 压缩图片:使用GD库或ImageMagick压缩图片文件
  • 异步处理:将文件处理任务放入队列异步执行
  • 分片上传:大文件使用分片上传,支持断点续传
  • 内存优化:使用流式处理大文件,避免内存溢出
  • 使用专用存储:考虑使用云存储服务(如AWS S3、阿里云OSS)
  • 负载均衡:在高并发环境下使用负载均衡分发上传请求