CodeIgniter 4 (CI4) 重寫了文件上傳類,提供流暢接口、強類型驗證、多存儲適配器支持。本文基於 CI4 最新版,覆蓋表單構建、控制器邏輯、配置詳解、安全加固及雲存儲擴展。
app/
├── Config/
│ └── Upload.php # 上傳配置文件(推薦)
├── Controllers/
│ └── Upload.php # 控制器
├── Views/
│ ├── upload_form.php # 上傳表單
│ └── upload_success.php # 成功頁面
└── Helpers/
└── form_helper.php # 可選:自定義表單助手
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>文件上傳</title>
<style>
.error {color: #e74c3c; margin: 1rem 0;}
.success {color: #27ae60; margin: 1rem 0;}
form {max-width: 500px; margin: 2rem auto; padding: 1.5rem; border: 1px solid #ddd; border-radius: 8px;}
.form-group {margin-bottom: 1rem;}
label {display: block; margin-bottom: .5rem; font-weight: 600;}
input[type="file"] {display: block; width: 100%;}
button {background: #3498db; color: white; border: none; padding: .75rem 1.5rem; border-radius: 4px; cursor: pointer;}
button:hover {background: #2980b9;}
</style>
</head>
<body>
<h1 style="text-align:center;">文件上傳演示</h1>
<?php if (session()->getFlashdata('error')): ?>
<div class="error"><?= session()->getFlashdata('error') ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="success"><?= session()->getFlashdata('success') ?></div>
<?php endif; ?>
<form action="<?= site_url('upload/doUpload') ?>" method="post" enctype="multipart/form-data">
<?= csrf_field() ?>
<div class="form-group">
<label for="userfile">選擇圖片 (JPG/PNG/GIF, ≤2MB)</label>
<input type="file" name="userfile" id="userfile" accept="image/jpeg,image/png,image/gif" required>
</div>
<button type="submit">上傳</button>
</form>
</body>
</html>
<?php $data = $upload_data ?? []; ?>
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>上傳成功</title></head>
<body style="text-align:center; padding:2rem;">
<h1>🎉 文件上傳成功!</h1>
<div style="max-width:500px; margin:2rem auto; text-align:left;">
<p><strong>文件名:</strong> <?= esc($data['client_name'] ?? '') ?></p>
<p><strong>存儲名:</strong> <?= esc($data['name'] ?? '') ?></p>
<p><strong>類型:</strong> <?= esc($data['type'] ?? '') ?></p>
<p><strong>大小:</strong> <?= number_format(($data['size'] ?? 0)/1024, 2) ?> KB</p>
<p><strong>路徑:</strong> <?= esc($data['full_path'] ?? '') ?></p>
<?php if (($data['is_image'] ?? 0) === 1): ?>
<p><strong>尺寸:</strong> <?= $data['image_width'] ?> × <?= $data['image_height'] ?></p>
<img src="<?= base_url('uploads/' . $data['name']) ?>" alt="預覽" style="max-width:100%; border:1px solid #ddd;">
<?php endif; ?>
</div>
<p><a href="<?= site_url('upload') ?>">繼續上傳</a></p>
</body>
</html>
<?php namespace Config;
use CodeIgniter\Config\BaseConfig;
class Upload extends BaseConfig
{
// 默認配置(可被控制器覆蓋)
public $default = [
'upload_path' => FCPATH . 'uploads/', // 絕對路徑,需可寫
'allowed_types' => 'jpg|jpeg|png|gif|webp',
'max_size' => 2048, // KB
'max_width' => 1920,
'max_height' => 1080,
'encrypt_name' => true, // 防衝突、防猜測
'remove_spaces' => true, // 文件名空格→下劃線
'overwrite' => false, // 不覆蓋,自動重命名
'file_name' => '', // 為空則用 encrypt_name
];
// 業務場景差異化配置
public $avatar = [
'max_size' => 512,
'max_width' => 400,
'max_height' => 400,
'allowed_types' => 'jpg|png',
];
public $document = [
'allowed_types' => 'pdf|doc|docx|xls|xlsx|txt',
'max_size' => 10240, // 10MB
];
}
<?php namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\Files\File;
use Config\Services;
class Upload extends Controller
{
public function index()
{
return view('upload_form');
}
public function doUpload()
{
// 1. 獲取配置(合併默認 + 場景配置)
$config = config('Upload')->default;
if ($this->request->getPost('type')) {
$type = $this->request->getPost('type');
$config = array_merge($config, config('Upload')->{$type} ?? []);
}
// 2. 初始化上傳庫
$upload = Services::upload($config);
// 3. 執行上傳(字段名默認 userfile)
if (!$upload->doUpload('userfile')) {
// 失敗:返回錯誤信息
return redirect()->back()
->withInput()
->with('error', $upload->displayErrors());
}
// 4. 成功:獲取文件元數據
$fileData = $upload->getData();
// 5. 業務擴展點:保存到數據庫、生成縮略圖、推送隊列等
$this->postProcess($fileData);
// 6. 返回成功頁面
return view('upload_success', ['upload_data' => $fileData]);
}
/**
* 上傳後處理(可提取到 Service/Listener)
*/
protected function postProcess(array $data): void
{
// 示例:記錄日誌
log_message('info', '文件上傳: ' . json_encode([
'name' => $data['name'],
'size' => $data['size'],
'type' => $data['type'],
'user_id' => auth()->id() ?? 'guest',
]));
// 示例:生成縮略圖(需安裝 intervention/image)
// if ($data['is_image']) { ... }
}
}
$routes->get('upload', 'Upload::index');
$routes->post('upload/doUpload', 'Upload::doUpload');
| 風險 | 對策 | 代碼位置 |
|---|---|---|
| 惡意腳本上傳 | 1. allowed_types 嚴格白名單 2. encrypt_name=true 3. 存儲目錄禁用 PHP 執行 | Config/Upload.php + Nginx/Apache 配置 |
| 文件名注入/路徑遍歷 | remove_spaces=true、encrypt_name=true、CI4 內置路徑安全檢查 | 配置自動生效 |
| 大文件 DoS | max_size + PHP upload_max_filesize + Nginx client_max_body_size 三層限制 | php.ini / nginx.conf / Config |
| MIME 偽裝 | CI4 使用 finfo 檢測真實類型,非僅信任瀏覽器傳來的 type | 核心庫自動處理 |
| CSRF | 表單包含 = csrf_field() ?>,控制器啟用 $this->request->getPost('csrf_token') 驗證 | View + Controller |
CI4 支持自定義存儲處理器,實現 CodeIgniter\Files\FileInterface 即可對接 S3、OSS、COS、七牛等:
<?php namespace App\Libraries\Storage;
use CodeIgniter\Files\File;
use Aws\S3\S3Client;
class S3Handler implements \CodeIgniter\Files\FileInterface
{
private S3Client $client;
private string $bucket;
public function __construct(string $key, string $secret, string $region, string $bucket)
{
$this->client = new S3Client(['version'=>'latest','region'=>$region,'credentials'=>compact('key','secret')]);
$this->bucket = $bucket;
}
// 實現 move(), delete(), getPath(), getURL() 等方法...
}
// 使用:在控制器中
$upload = Services::upload($config);
$upload->setFileHandler(new S3Handler(...));
chmod 775 uploads/、確保 PHP-FPM 用戶(www-data)有權限php.ini post_max_size、upload_max_filesize、max_execution_time;Nginx client_max_body_size 20M;base_url 配置正確、Nginx 靜態資源 location 配置、文件實際存在最佳實踐總結:配置集中化 → 控制器薄化 → 業務服務化 → 安全分層 → 存儲抽象化。CI4 上傳類已足夠強大,善用配置文件與服務層,避免控制器膨脹。