2025年12月28日 星期日

利用 laravel 12 做一個類似Google Drive 的作品-權限與安全

零、目標:
1.專案目標:
   A.環境準備
   B.建立 Laravel 專案 + 登入系統
   C.建立資料庫(資料夾 / 檔案)
   D.資料夾 CRUD
   E.檔案上傳 / 下載
   F.檔案列表(像 Google Drive)
   G.分享連結
   H.權限與安全
    I.回收桶(Soft Delete)
2.目前目標:
   H.權限與安全
       a.用 Policy 做檔案與資料夾權限控管
       b.防止未授權存取(猜網址就能看別人檔案)
       c.Controller 符合 Laravel Best Practice

一、建立 FolderPolicy
    1.指令:php artisan make:policy FolderPolicy --model=Folder
    2.編輯 app/Policies/FolderPolicy.php

    如下圖

二、建立 FilePolicy
    1.指令:php artisan make:policy FilePolicy --model=File
    2.編輯 app/Policies/FilePolicy.php

    如下圖

三、註冊 Policy
    1.編輯 app/Providers/AuthServiceProvider.php

    如下圖

四、Controller 改為 authorize(重點)
    1.目的:Controller 變乾淨,安全集中管理
    2.編輯下列檔案
       a.FolderController@show

       b.FileController@download

       c.ShareController@create

五、Blade 層級權限
    1.編輯 folders/show.blade.php

六、測試

利用 laravel 12 做一個類似Google Drive 的作品-分享連結


零、目標:
1.專案目標:
   A.環境準備
   B.建立 Laravel 專案 + 登入系統
   C.建立資料庫(資料夾 / 檔案)
   D.資料夾 CRUD
   E.檔案上傳 / 下載
   F.檔案列表(像 Google Drive)
   G.分享連結
   H.權限與安全
    I.回收桶(Soft Delete)
2.目前目標:
   G.分享連結(不用登入也能下載)
       a.產生公開分享連結
       b.未登入也可以下載檔案
       c.分享連結使用 token (安全)
       d.可設定到期時間(基本版)

一、建立 shares 資料夾
    1.指令:php artisan make:model Share -m
    2.編輯 database/migrations/xxxx_create_shares_table.php
$table->foreignId('file_id')->constrained()->cascadeOnDelete();
$table->string('token')->unique();
$table->timestamp('expires_at')->nullable();
    如下圖
    3.指令:php artisan migrate

二、Share Model
    1.編輯 app/Models/Share.php
    protected $fillable = [
        'file_id',
        'token',
        'expires_at',
    ];

    public function file(){
        return $this->belongsTo(File::class);
    }

    如下圖

三、修改 File Model (加關聯)
    1.編輯 app/Models/File.php
use App\Models\Share;
    
    public function share(){
    return $this->hasOne(Share::class);
    }
    如下圖

四、建立 ShareController
    1.指令:php artisan make:controller ShareController
    2.編輯 app/Http/Controllers/ShareController.php
use App\Models\File;
use App\Models\Share;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
    與
    // 建立分享連結
    public function create(File $file){
        abort_if($file->user_id !== auth()->id(), 403);

        $share = Share::firstOrCreate(
            ['file_id' => $file->id],
            ['token' => Str::uuid()]
        );

        return back()->with('share_link', route('share.download', $share->token));
    }

    // 公開下載
    public function download($token){
        $share = Share::where('token', $token)->firstOrFail();
        $file = $share->file;

        return Storage::disk('public')->download($file->path, $file->name);
    }

    如下圖

五、設定路由
    1.編輯 routes/web.php
    // Share
    Route::post('/files/{file}/share', [ShareController::class, 'create'])
        ->name('files.share');
    與
// Share
Route::get('/share/{token}', [ShareController::class, 'download'])
    ->name('share.download');
    如下圖

六、修改檔案列表畫面(加入分享按鈕)
    1.編輯 folders/show.blade.php
        {{-- 檔案列表 --}}
        <h2 class="font-semibold mb-2">檔案</h2>

        <ul class="space-y-2">
            @forelse($files as $file)
                <li class="border p-3 rounded flex justify-between items-center">
                    📄 {{ $file->name }}
                    <div class="space-x-3">
                        <a href="{{ route('files.download', $file) }}"
                            class="text-blue-600 hover:underline">
                        下載
                    </a>
                    <form method="POST" action="{{ route('files.share', $file) }}" class="inline">
                        @csrf
                        <button class="text-green-600 hover:underline">
                        分享
                        </button>
                    </form>
                    </div>
                </li>
                @empty
                <li class="text-gray-400">尚無檔案</li>
            @endforelse
        </ul>
        {{-- 顯示分享連結 --}}
        @if(session('share_link'))
            <div class="mt-4 p-3 bg-green-100 border rounded">
                分享連結:
            <a href="{{ session('share_link') }}"
                class="text-blue-600 underline"
                target="_blank">
                {{ session('share_link') }}
            </a>
            </div>
        @endif

    如下圖

七、測試流程
    1.點進資料夾
    2.上傳檔案
    3.點[分享]
    4.複製連結
    5.登出或開無痕視窗
    6.貼上連結->成功下載


利用 laravel 12 做一個類似Google Drive 的作品-檔案上傳下載


零、目標:
1.專案目標:
   A.環境準備
   B.建立 Laravel 專案 + 登入系統
   C.建立資料庫(資料夾 / 檔案)
   D.資料夾 CRUD
   E.檔案上傳 / 下載
   F.檔案列表(像 Google Drive)
   G.分享連結
   H.權限與安全
    I.回收桶(Soft Delete)
2.目前目標:
   E.檔案上傳 / 下載+F.檔案列表(像 Google Drive)
       a.上傳檔案到指定資料夾
       b.在資料夾中看到[資料夾+檔案]混合列表
       c.下載檔案
       d.檔案真實存到 storage

一、建立 File Model + Migration
    1.指令:php artisan make:model File -m
    2.編輯 database/migrations/xxxx_create_files_table.php
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('folder_id')->nullable()
  ->constrained('folders')->cascadeOnDelete();
$table->string('name');          // 原始檔名
$table->string('path');          // 儲存路徑
$table->unsignedBigInteger('size');
    如下圖:
    3.指令:php artisan migrate

二、File Model
    1.編輯 app/models/File.php
    protected $fillable = [
        'user_id',
        'folder_id',
        'name',
        'path',
        'size',
    ];
    public function folder(){
        return $this->belongsTo(Folder::class);
    }
    如下圖:

三、建立 FileController
    1.指令:php artisan make:controller FileController
    2.編輯 app/Http/Controllers/FileController.php
use App\Models\File;
use App\Models\Folder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
    public function store(Request $request){
        $request->validate([
            'file' => 'required|file|max:10240', // 10MB
        ]);
        $uploaded = $request->file('file');
        $path = $uploaded->store('files', 'public');
        File::create([
            'user_id' => Auth::id(),
            'folder_id' => $request->folder_id,
            'name' => $uploaded->getClientOriginalName(),
            'path' => $path,
            'size' => $uploaded->getSize(),
        ]);
        return back();
    }

    public function download(File $file){
        abort_if($file->user_id !== Auth::id(), 403);
        return Storage::disk('public')->download($file->path, $file->name);
    }

    如下圖:

四、設定路由
    1. 編輯 routes/web.php
use App\Http\Controllers\FileController;
    // File
    Route::post('/files', [FileController::class, 'store'])->name('files.store');
    Route::get('/files/{file}/download', [FileController::class, 'download'])
        ->name('files.download');

    如下圖

五、顯示檔案(修改 folders.show)
    1.編輯 FolderController@show
use App\Models\File;
    與
// File
$files = File::where('folder_id', $folder->id)->get();
return view('folders.show', compact('folder', 'subFolders','files'));
    如下圖

六、修改畫面:上傳+檔案列表
    1.編輯 resources/views/folders/show.blade.php
      {{-- 上傳表單 --}}
        <form method="POST" action="{{ route('files.store') }}" enctype="multipart/form-data" class="mb-4">
            @csrf
            <input type="hidden" name="folder_id" value="{{ $folder->id }}">
            <input type="file" name="file">
            <button class="bg-green-600 text-white px-4 py-2 rounded">
                上傳檔案
            </button>
        </form>

        {{-- 檔案列表 --}}
        <h2 class="font-semibold mb-2">檔案</h2>

        <ul class="space-y-2">
            @forelse($files as $file)
                <li class="border p-3 rounded flex justify-between">
                    📄 {{ $file->name }}
                    <a href="{{ route('files.download', $file) }}"
                    class="text-blue-600 hover:underline">
                    下載
                    </a>
                </li>
                @empty
                <li class="text-gray-400">尚無檔案</li>
            @endforelse
        </ul>
    如下圖

七、建立 storage 連結(必要)
    1.指令:php artisan storage:link

八、測試流程
    1.點擊某資料夾
    2.上傳檔案
    3.看到檔案列表
    4.點[下載]成功


利用 laravel 12 做一個類似Google Drive 的作品-巢狀資料夾

零、目標:
1.專案目標:
   A.環境準備
   B.建立 Laravel 專案 + 登入系統
   C.建立資料庫(資料夾 / 檔案)
   D.資料夾 CRUD
   E.檔案上傳 / 下載
   F.檔案列表(像 Google Drive)
   G.分享連結
   H.權限與安全
    I.回收桶(Soft Delete)
2.目前目標:
   D.資料夾CRUD
       a.點擊資料夾進入
       b.在資料夾內新增子資料夾
       c.無限層資料夾
       d.基本路徑導覽

一、新增 Show 路由
    1.編輯 routes/web.php
Route::get('/folders/{folder}',[FolderController::class,'show'])->name('folders.show');
     如下圖:
    
二、FolderController 加入 show 方法
    1.編輯 app/Http/Controllers/FolderController.php
    public function show(Folder $folder){
        // 安全檢查:只能看自己的資料夾
        abort_if($folder->user_id !== Auth::id(), 403);
        $subFolders = Folder::where('parent_id', $folder->id)->get();
        return view('folders.show', compact('folder', 'subFolders'));
    }
    如下圖:

三、修改資料夾列表(可點擊)
    1.編輯 resources/view/folders/index.blade.php
@foreach($folders as $folder)
    <li class="border p-3 rounded hover:bg-gray-100">
        📁
        <a href="{{ route('folders.show', $folder) }}"
           class="text-blue-600 font-semibold">
           {{ $folder->name }}
        </a>
     </li>
@endforeach
    如下圖:

四、建立資料夾內容頁
    1.指令:cd resources/views/folders
                  type nul > show.blade.php
    2.編輯 resources/view/folders/show.blade.php
<x-app-layout>
    <div class="p-6 max-w-4xl mx-auto">

        {{-- breadcrumb --}}
        <div class="mb-4 text-sm text-gray-500">
            <a href="{{ route('folders.index') }}" class="hover:underline">
                我的雲端硬碟
            </a>
            /
            {{ $folder->name }}
        </div>

        <h1 class="text-xl font-bold mb-4">📁 {{ $folder->name }}</h1>

        {{-- 新增子資料夾 --}}
        <form method="POST" action="{{ route('folders.store') }}" class="mb-4">
            @csrf
            <input type="hidden" name="parent_id" value="{{ $folder->id }}">
            <input type="text" name="name" placeholder="子資料夾名稱"
                   class="border rounded p-2 mr-2">
            <button class="bg-blue-500 text-white px-4 py-2 rounded">
                新增
            </button>
        </form>

        {{-- 子資料夾列表 --}}
        <ul class="space-y-2">
            @forelse($subFolders as $sub)
                <li class="border p-3 rounded hover:bg-gray-100">
                    📁
                    <a href="{{ route('folders.show', $sub) }}"
                       class="text-blue-600 font-semibold">
                        {{ $sub->name }}
                    </a>
                </li>
            @empty
                <li class="text-gray-400">此資料夾尚無子資料夾</li>
            @endforelse
        </ul>

    </div>
</x-app-layout>

    如下圖:
五、測試流程
    1.開 /folders
    2.建立資料夾01
    3.點進01
    4.建立資料夾02
    5.點進02
    如果可以一直點進去,巢狀成功


利用 laravel 12 做一個類似Google Drive 的作品-權限與安全

相關系列文章: 1. 利用 laravel 12 做一個類似Google Drive 的作品-使用者系統 2. 利用 laravel 12 做一個類似Google Drive 的作品-資料夾系統 3. 利用 laravel 12 做一個類似Google Drive 的作品-巢狀資...