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

ThinkPHP 6/8 文件上傳完整指南:驗證、儲存、圖片處理與雲存儲整合

     編輯:PHP 綜合知識

前言:ThinkPHP 內建上傳組件,勿重複造輪子

ThinkPHP 6/8 提供 `think\facade\Filesystem` 與 `think\File` 類,支援本地、FTP、S3、OSS、COS 等多種驅動,內建驗證、自動命名、圖片處理。本文系統講解最佳實踐。

基礎配置 (config/filesystem.php)

return [
    'default' => env('filesystem.driver', 'local'),
    'disks' => [
        'local' => [
            'type' => 'local',
            'root' => public_path() . '/uploads', // 公開可訪問目錄
            'visibility' => 'public',
        ],
        'private' => [
            'type' => 'local',
            'root' => storage_path('app/private'), // 非公開目錄
            'visibility' => 'private',
        ],
        'aliyun_oss' => [
            'type' => 'aliyun',
            'access_key' => env('OSS_KEY'),
            'secret_key' => env('OSS_SECRET'),
            'bucket' => env('OSS_BUCKET'),
            'endpoint' => env('OSS_ENDPOINT'),
            'domain' => env('OSS_DOMAIN'), // 自定義域名
        ],
        'qiniu' => [
            'type' => 'qiniu',
            'access_key' => env('QINIU_AK'),
            'secret_key' => env('QINIU_SK'),
            'bucket' => env('QINIU_BUCKET'),
            'domain' => env('QINIU_DOMAIN'),
        ],
    ],
];

控制器實現:單檔/多檔上傳與驗證

 'file|max:10240|ext:jpg,png,gif,webp|image:width<2000,height<2000',
        'files.*' => 'file|max:5120|ext:jpg,png,gif,webp',
    ];
    
    protected $uploadMsg = [
        'file.max' => '檔案大小不能超過 10MB',
        'file.ext' => '僅支援 jpg, png, gif, webp 格式',
        'file.image' => '圖片尺寸不能超過 2000x2000',
    ];

    // 單檔上傳
    public function single(): Response
    {
        $file = $this->request->file('file');
        if (empty($file)) {
            return json(['code' => 400, 'msg' => '未接收到檔案']);
        }
        
        try {
            // 驗證
            Validate::rule($this->uploadRule)->message($this->uploadMsg)->check(['file' => $file]);
            
            // 儲存 (自動生成唯一文件名:日期目錄 + md5 + 副檔名)
            $savename = $file->move(Filesystem::disk('local'))->getSaveName();
            $url = Filesystem::disk('local')->url($savename);
            
            // 可選:生成縮圖
            $this->makeThumb($savename);
            
            return json([
                'code' => 200,
                'msg' => '上傳成功',
                'data' => [
                    'name' => $file->getOriginalName(),
                    'savename' => $savename,
                    'url' => $url,
                    'size' => $file->getSize(),
                    'type' => $file->getMimeType(),
                ]
            ]);
        } catch (ValidateException $e) {
            return json(['code' => 422, 'msg' => $e->getMessage()]);
        } catch (\Throwable $e) {
            \think\facade\Log::error('Upload failed: ' . $e->getMessage());
            return json(['code' => 500, 'msg' => '伺服器內部錯誤']);
        }
    }
    
    // 多檔上傳
    public function multiple(): Response
    {
        $files = $this->request->file('files');
        if (empty($files)) {
            return json(['code' => 400, 'msg' => '未接收到檔案']);
        }
        
        $results = [];
        foreach ($files as $file) {
            try {
                Validate::rule($this->uploadRule)->message($this->uploadMsg)->check(['file' => $file]);
                $savename = $file->move(Filesystem::disk('local'))->getSaveName();
                $results[] = [
                    'name' => $file->getOriginalName(),
                    'savename' => $savename,
                    'url' => Filesystem::disk('local')->url($savename),
                    'size' => $file->getSize(),
                ];
            } catch (ValidateException $e) {
                $results[] = ['name' => $file->getOriginalName(), 'error' => $e->getMessage()];
            }
        }
        
        return json(['code' => 200, 'msg' => '批次上傳完成', 'data' => $results]);
    }
    
    // 圖片縮圖生成 (依賴 intervention/image)
    private function makeThumb(string $savename): void
    {
        $image = \Image::make(Filesystem::disk('local')->path($savename));
        $image->fit(300, 300, function ($constraint) {
            $constraint->upsize();
        });
        $thumbName = 'thumb_' . $savename;
        $image->save(Filesystem::disk('local')->path($thumbName));
    }
}

前端整合:原生 FormData + Axios

進階功能:圖片處理、水印、格式轉換

use Intervention\Image\Facades\Image;

// 上傳後自動處理
public function processImage(string $savename): array
{
    $path = Filesystem::disk('local')->path($savename);
    $img = Image::make($path);
    
    // 1. 自動修正 EXIF 方向
    $img->orientate();
    
    // 2. 生成多尺寸縮圖
    $sizes = [
        'thumb' => [150, 150],
        'medium' => [600, 600],
        'large' => [1200, 1200],
    ];
    $thumbs = [];
    foreach ($sizes as $name => [$w, $h]) {
        $thumbName = "{$name}_{$savename}";
        $img->clone()->fit($w, $h)->save(Filesystem::disk('local')->path($thumbName));
        $thumbs[$name] = Filesystem::disk('local')->url($thumbName);
    }
    
    // 3. 添加水印 (右下角,透明度 30%)
    $watermark = Image::make(public_path('watermark.png'));
    $img->insert($watermark, 'bottom-right', 10, 10)->save($path);
    
    // 4. 轉 WebP (現代瀏覽器更小)
    $webpName = pathinfo($savename, PATHINFO_FILENAME) . '.webp';
    $img->encode('webp', 85)->save(Filesystem::disk('local')->path($webpName));
    
    return [
        'original' => Filesystem::disk('local')->url($savename),
        'webp' => Filesystem::disk('local')->url($webpName),
        'thumbs' => $thumbs,
    ];
}

雲存儲驅動整合 (以阿里云 OSS 為例)

  1. 安裝擴展包:`composer require league/flysystem-aliyun-oss`
  2. 註冊驅動 (config/filesystem.php 已配置 `aliyun_oss`)
  3. 切換存儲盤:
    // 上傳至 OSS
    $savename = $file->move(Filesystem::disk('aliyun_oss'))->getSaveName();
    $url = Filesystem::disk('aliyun_oss')->url($savename); // 自動生成簽名 URL 或公開 URL
    
    // 臨時授權下載 (私有桶)
    $url = Filesystem::disk('aliyun_oss')->temporaryUrl($savename, now()->addMinutes(30));

安全加固清單

風險防護措施
惡意腳本上傳 (.php, .jsp 等)嚴格 `ext` 白名單、`image` 驗證、儲存目錄禁用腳本執行權限 (Nginx `location ~ \.php$ { deny all; }`)
檔名路徑遍歷 (../../etc/passwd)ThinkPHP `move()` 自動生成安全檔名,不使用用戶原始檔名
大檔案佔滿磁碟`max` 限制、定時清理臨時檔、磁碟配額監控
圖片炸彈 (壓縮極大、像素極大)`image:width<2000,height<2000`、Intervention Image 讀取前檢查尺寸
上傳介面被濫用 (刷存儲、刷流量)登入態驗證、頻率限制 (RateLimiter)、驗證碼、簽名直傳 (OSS 直傳)

直傳模式:前端直傳 OSS,後端僅簽名 (高併發推薦)

// 後端:生成 OSS 直傳簽名 (Post Policy)
public function getOssPolicy(): Response
{
    $oss = Filesystem::disk('aliyun_oss')->getAdapter()->getClient();
    $policy = [
        'expiration' => date('c', time() + 3600),
        'conditions' => [
            ['content-length-range', 0, 10 * 1024 * 1024], // 10MB
            ['starts-with', '$key', 'uploads/'],
        ],
    ];
    $policyBase64 = base64_encode(json_encode($policy));
    $signature = base64_encode(hash_hmac('sha1', $policyBase64, config('filesystem.disks.aliyun_oss.secret_key'), true));
    
    return json([
        'policy' => $policyBase64,
        'signature' => $signature,
        'access_key' => config('filesystem.disks.aliyun_oss.access_key'),
        'host' => config('filesystem.disks.aliyun_oss.domain'),
        'dir' => 'uploads/' . date('Ymd') . '/',
    ]);
}

結語

ThinkPHP 文件上傳核心優勢在於 **統一 Filesystem 抽象、內建驗證規則、多驅動無縫切換**。掌握「驗證→儲存→處理→回應」標準流程,配合安全加固與雲存儲直傳,能應對從簡單頭像上傳到億級附件系統的各種場景。

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