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

PHP + jQuery Ajax 非同步圖片上傳實作(jQuery Form Plugin)

     編輯:PHP 相關知識

前言

傳統表單上傳需頁面跳轉,體驗差。利用 jQuery Form Plugin(ajaxSubmit())可輕鬆實現真正的非同步檔案上傳(非 iframe hack),配合 PHP 後端驗證、分類儲存、隨機命名,構建完整上傳體驗。本文提供前後端完整程式碼與安全強化指南。

前端頁面

<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <title>非同步圖片上傳示範</title>
    <style>
        .upload-area { border: 2px dashed #ccc; padding: 20px; text-align: center; max-width: 500px; margin: 20px auto; border-radius: 8px; }
        .upload-area.dragover { border-color: #4285f4; background: #f0f7ff; }
        .progress-bar { width: 100%; height: 8px; background: #eee; border-radius: 4px; overflow: hidden; display: none; margin-top: 10px; }
        .progress-bar .fill { height: 100%; width: 0%; background: #4285f4; transition: width 0.3s; }
        .msg { text-align: center; margin-top: 10px; font-weight: 500; }
        .msg.success { color: #34a853; }
        .msg.error { color: #ea4335; }
        .preview { max-width: 100%; max-height: 300px; margin-top: 15px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); display: none; }
    </style>
</head>
<body>
    <div class="upload-area" id="dropZone">
        <input type="file" id="fileInput" name="upfile" accept="image/*" style="display: none;">
        <p>點擊或拖曳圖片到此處上傳</p>
        <button type="button" id="selectBtn" class="btn">選擇檔案</button>
        <div class="progress-bar" id="progressBar"><div class="fill"></div></div>
        <div class="msg" id="msg"></div>
        <img class="preview" id="preview" src="" alt="預覽">
    </div>

    <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/jquery-form@4.3.0/dist/jquery.form.min.js"></script>
    <script>
    $(function () {
        const $dropZone = $('#dropZone');
        const $fileInput = $('#fileInput');
        const $progressBar = $('#progressBar');
        const $progressFill = $progressBar.find('.fill');
        const $msg = $('#msg');
        const $preview = $('#preview');

        // 點擊選擇
        $('#selectBtn').on('click', () => $fileInput.trigger('click'));

        // 拖曳上傳支援
        $dropZone.on('dragover dragenter', e => {
            e.preventDefault();
            $dropZone.addClass('dragover');
        }).on('dragleave drop', e => {
            e.preventDefault();
            $dropZone.removeClass('dragover');
        }).on('drop', e => {
            const files = e.originalEvent.dataTransfer.files;
            if (files.length) {
                $fileInput[0].files = files;
                uploadFile();
            }
        });

        // 檔案選擇變更
        $fileInput.on('change', uploadFile);

        function uploadFile() {
            const file = $fileInput[0].files[0];
            if (!file) return;

            // 客戶端預檢
            const maxSize = 5 * 1024 * 1024; // 5MB
            const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
            if (!allowedTypes.includes(file.type)) {
                showMsg('不支援的檔案格式,僅限 JPG/PNG/GIF/WebP', 'error');
                return;
            }
            if (file.size > maxSize) {
                showMsg(`檔案過大(${(file.size/1024/1024).toFixed(2)}MB),限制 5MB`, 'error');
                return;
            }

            // 預覽
            const reader = new FileReader();
            reader.onload = e => {
                $preview.attr('src', e.target.result).show();
            };
            reader.readAsDataURL(file);

            // 非同步上傳
            $progressBar.show();
            $progressFill.css('width', '0%');
            showMsg('上傳中...', '');

            $('#uploadForm').ajaxSubmit({
                url: 'upload.php',
                type: 'POST',
                dataType: 'json',
                uploadProgress: (event, position, total, percentComplete) => {
                    $progressFill.css('width', percentComplete + '%');
                },
                success: (response) => {
                    $progressBar.hide();
                    if (response.success) {
                        showMsg('上傳成功!', 'success');
                        $preview.attr('src', response.url + '?t=' + Date.now());
                    } else {
                        showMsg('上傳失敗:' + response.message, 'error');
                        $preview.hide();
                    }
                },
                error: (xhr) => {
                    $progressBar.hide();
                    showMsg('網路錯誤:' + xhr.statusText, 'error');
                    $preview.hide();
                },
                resetForm: true
            });
        }

        function showMsg(text, type) {
            $msg.text(text).removeClass('success error').addClass(type);
        }
    });
    </script>

    <form id="uploadForm" action="upload.php" method="POST" enctype="multipart/form-data" style="display: none;">
        <input type="file" name="upfile" id="fileInput">
    </form>
</body>
</html>

PHP 後端處理

<?php
// upload.php
header('Content-Type: application/json; charset=utf-8');

// 配置
$config = [
    'maxSize'      => 5 * 1024 * 1024,      // 5MB
    'allowedTypes' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
    'allowedExts'  => ['jpg', 'jpeg', 'png', 'gif', 'webp'],
    'uploadRoot'   => __DIR__ . '/uploads', // 儲存根目錄
    'urlPrefix'    => '/uploads',           // 公開訪問 URL 前綴
];

// 回應函式
function respond(bool $success, string $message, array $extra = []): never
{
    echo json_encode(array_merge(['success' => $success, 'message' => $message], $extra), JSON_UNESCAPED_UNICODE);
    exit;
}

// 1. 檢查請求
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    respond(false, '僅支援 POST 請求');
}

if (!isset($_FILES['upfile']) || $_FILES['upfile']['error'] !== UPLOAD_ERR_OK) {
    $errMsg = $_FILES['upfile']['error'] ?? '未知錯誤';
    respond(false, "檔案上傳錯誤: $errMsg");
}

$file = $_FILES['upfile'];

// 2. 檔案大小驗證
if ($file['size'] > $config['maxSize']) {
    respond(false, '檔案超過大小限制 (5MB)');
}

// 3. MIME 類型驗證(使用 finfo 避免偽造)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
if (!in_array($mimeType, $config['allowedTypes'], true)) {
    respond(false, '檔案類型不允許(偵測到: ' . $mimeType . ')');
}

// 4. 副檔名驗證
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $config['allowedExts'], true)) {
    respond(false, '副檔名不允許');
}

// 5. 圖片完整性驗證
$imgInfo = @getimagesize($file['tmp_name']);
if ($imgInfo === false) {
    respond(false, '非有效圖片檔案或圖片損毀');
}
// 可選:限制最大尺寸
// if ($imgInfo[0] > 4000 || $imgInfo[1] > 4000) { respond(false, '圖片尺寸過大'); }

// 6. 建立日期目錄
$dateDir = date('Y/m/d');
$saveDir = $config['uploadRoot'] . '/' . $dateDir;
if (!is_dir($saveDir)) {
    if (!mkdir($saveDir, 0755, true) && !is_dir($saveDir)) {
        respond(false, '無法建立上傳目錄');
    }
}

// 7. 產生唯一檔名(時間戳 + 隨機字串 + 原始副檔名)
$randomStr = bin2hex(random_bytes(8));
$fileName = sprintf('%s_%s.%s', date('His'), $randomStr, $ext);
$savePath = $saveDir . '/' . $fileName;
$publicUrl = $config['urlPrefix'] . '/' . $dateDir . '/' . $fileName;

// 8. 移動檔案
if (!move_uploaded_file($file['tmp_name'], $savePath)) {
    respond(false, '檔案儲存失敗(權限或磁碟空間不足)');
}

// 9. 設定檔案權限
chmod($savePath, 0644);

// 10. 可選:生成縮圖、水印、記錄資料庫...
// generateThumbnail($savePath, 300, 300);

// 成功回應
respond(true, '上傳成功', [
    'url' => $publicUrl,
    'fileName' => $fileName,
    'size' => $file['size'],
    'mime' => $mimeType,
    'width' => $imgInfo[0],
    'height' => $imgInfo[1],
]);

安全強化清單

  1. MIME 雙重驗證:$_FILES['type'] 可偽造,必須用 finfo 或 getimagesize() 實際檢測檔案內容。
  2. 副檔名白名單:防止上傳 .php、.phtml 等可執行腳本。
  3. 儲存目錄不可執行:Nginx/Apache 配置 location /uploads { php_flag engine off; } 或 AddHandler text/plain .php。
  4. 隨機檔名:避免覆蓋、路徑遍歷、使用者猜測他人檔案。
  5. 大小限制:PHP upload_max_filesize、post_max_size、Nginx client_max_body_size 三層同步設定。
  6. 圖片重新處理:imagecreatefromjpeg() → imagejpeg() 重新編碼,剝離 EXIF、隱寫、多幀攻擊載荷。
  7. 病毒掃描:整合 ClamAV 掃描上傳檔案(高安全需求系統)。
  8. CSRF 保護:表單攜帶 Token,後端驗證。

進階功能擴展

多檔案上傳

分塊上傳/斷點續傳

雲端儲存直傳

除錯常見問題

現象可能原因排查步驟
回應非 JSONPHP 錯誤輸出 HTML、BOM、前置空白檢查 php.ini display_errors=Off、檔案無 BOM、無前置輸出
進度條不動jQuery Form Plugin 未載入、瀏覽器不支援 XHR2Console 確認 $.fn.ajaxSubmit 存在、IE10+ 支援
move_uploaded_file 失敗目錄權限、磁碟滿、open_basedir 限制ls -ld /uploads、df -h、php -i | grep open_basedir
上傳後圖片無法顯示Nginx 未配置 static location、MIME type 缺失確認 location /uploads { alias /path/to/uploads; }

掌握非同步上傳全流程,從前端體驗優化到後端安全防護,再到雲端架構演進,能為使用者提供流暢、安全、可擴展的檔案上傳服務。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved