檔案上傳大小限制必須在「伺服端」強制執行(客戶端可繞過)。完整防護包含三層:
upload_max_filesize、post_max_size、memory_limitclient_max_body_size、Apache LimitRequestBody; php.ini
file_uploads = On
upload_max_filesize = 10M ; 单个文件上限
post_max_size = 12M ; POST 總資料上限(需 > upload_max_filesize)
memory_limit = 128M ; 腳本記憶體上限
max_file_uploads = 20 ; 同時上傳檔案數
max_execution_time = 300 ; 大檔上傳需較長執行時間
; 開發環境建議開啟錯誤顯示
display_errors = On
error_reporting = E_ALL
注意:修改後必須重啟 PHP-FPM / Apache 生效。若使用共享主機無法改 php.ini,可嘗試 .user.ini 或 ini_set()(僅限非系統級指令)。
# nginx.conf / site.conf
http {
client_max_body_size 12M; # 需 >= post_max_size
# 其他優化
client_body_timeout 60s;
client_header_timeout 60s;
}
# httpd.conf / .htaccess
LimitRequestBody 12582912 # 12M (bytes)
# 或在 VirtualHost 內
LimitRequestBody 12582912
CKEditor 4 使用 editor/filemanager/connectors/php/ 作為預設上傳端點。
config.php<?php
// editor/filemanager/connectors/php/config.php
$Config['Enabled'] = true;
$Config['UserFilesPath'] = '/userfiles/';
$Config['UserFilesAbsolutePath'] = '/var/www/html/userfiles/';
// 自訂最大檔案大小(單位:KB)
$Config['MaxImageSize'] = 2048; // 2MB 圖片
$Config['MaxFileSize'] = 10240; // 10MB 一般檔案
commands.php 攔截超大檔案<?php
// editor/filemanager/connectors/php/commands.php
// 在 FileUpload 函式內,安全驗證區塊後加入:
if (isset($Config['MaxImageSize']) && $resourceType === 'Image') {
$sizeKB = round($oFile['size'] / 1024);
if ($sizeKB > $Config['MaxImageSize']) {
$sErrorNumber = '204'; // 自訂錯誤代碼:圖片過大
}
}
if (isset($Config['MaxFileSize']) && $resourceType === 'File') {
$sizeKB = round($oFile['size'] / 1024);
if ($sizeKB > $Config['MaxFileSize']) {
$sErrorNumber = '205'; // 一般檔案過大
}
}
// 重要:移除原本的 else { $sErrorNumber = '202'; } 區塊,
// 否則自訂錯誤會被覆蓋為通用「無效副檔名」錯誤。
fck_image.js / fck_file.js// editor/dialog/fck_image/fck_image.js
// 在 OnUploadCompleted 函式的 switch (errorNumber) 中新增:
case 204:
alert('圖片檔案過大,限制 2MB 以內');
return;
case 205:
alert('檔案過大,限制 10MB 以內');
return;
CKEditor 5 採用「上傳適配器」架構,完全可自訂上傳邏輯。
// MyCustomUploadAdapter.js
class MyCustomUploadAdapter {
constructor(loader) {
this.loader = loader;
}
upload() {
return this.loader.file
.then(file => new Promise((resolve, reject) => {
// 客戶端預檢(使用者體驗,非安全邊界)
const MAX_SIZE = 2 * 1024 * 1024; // 2MB
if (file.size > MAX_SIZE) {
reject('圖片不可超過 2MB');
return;
}
const data = new FormData();
data.append('upload', file);
fetch('/api/upload-image', {
method: 'POST',
body: data,
credentials: 'same-origin'
})
.then(response => response.json())
.then(result => {
if (result.error) {
reject(result.error.message);
} else {
resolve({ default: result.url });
}
})
.catch(err => reject('上傳失敗:' + err.message));
}));
}
abort() { /* 可選:中止上傳 */ }
}
// 在 CKEditor 設定中註冊
ClassicEditor
.create(document.querySelector('#editor'), {
extraPlugins: [MyCustomUploadAdapterPlugin],
// 其他設定...
})
.catch(console.error);
/api/upload-image (PHP)<?php
// api/upload-image.php
header('Content-Type: application/json; charset=utf-8');
$maxSize = 2 * 1024 * 1024; // 2MB
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$uploadDir = __DIR__ . '/../public/uploads/images/';
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => ['message' => 'Method Not Allowed']]);
exit;
}
if (!isset($_FILES['upload']) || $_FILES['upload']['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
echo json_encode(['error' => ['message' => '無效的上傳檔案']]);
exit;
}
$file = $_FILES['upload'];
// 伺服端二次驗證(安全邊界)
if ($file['size'] > $maxSize) {
http_response_code(413);
echo json_encode(['error' => ['message' => '檔案過大,限制 2MB']]);
exit;
}
// MIME 偵測(比副檔名可靠)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, $allowedTypes, true)) {
http_response_code(415);
echo json_encode(['error' => ['message' => '不支援的檔案類型']]);
exit;
}
// 產生唯一檔名
$ext = ['image/jpeg'=>'jpg','image/png'=>'png','image/gif'=>'gif','image/webp'=>'webp'][$mime];
$newName = bin2hex(random_bytes(16)) . '.' . $ext;
$dest = $uploadDir . $newName;
if (!move_uploaded_file($file['tmp_name'], $dest)) {
http_response_code(500);
echo json_encode(['error' => ['message' => '檔案儲存失敗']]);
exit;
}
// 回傳公開 URL
echo json_encode(['url' => '/uploads/images/' . $newName]);
?>
| 現象 | 可能原因 | 解決方向 |
|---|---|---|
| 上傳大檔直接報 413 / 500 | Nginx client_max_body_size 過小 | 調整 nginx.conf 並 reload |
| PHP $_FILES 為空 | post_max_size < 上傳總大小 | 調大 post_max_size (>= upload_max_filesize + 表單其他欄位) |
| CKEditor 4 回傳錯誤代碼 202 | 自訂錯誤被預設 else 覆蓋 | 移除 commands.php 中的 else 區塊 |
| 前端 alert 顯示亂碼 | JS 檔案編碼非 UTF-8 | 確保 .js 檔案以 UTF-8 儲存 |
| 上傳成功但圖片顯示不出來 | 回傳 URL 路徑錯誤 / 權限問題 | 檢查 UserFilesPath、實體路徑權限、Nginx static location |
CKEditor 檔案大小限制必須「三層同步」:PHP.ini、Web Server、應用層邏輯。CKEditor 4 需修改連接器原始碼;CKEditor 5 建議自寫上傳適配器,掌控完整流程。生產環境務必在後端以 finfo 驗證 MIME、以隨機檔名儲存、限制執行權限,防止惡意檔案上傳執行。