iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Modern Web

Laravel Filament 從入門到實戰系列 第 17 篇

Day 16:設計一份貼近真實團隊的角色權限矩陣

  • 分享至 

  • xImage
  •  

第四階段從補齊表單驗證細節,走到生命週期掛載點,再到 Day 15:接上 Laravel 既有的 Policy,誰能看見這個操作按鈕 接上的 Policy 機制,權限判斷的地基算是打好了。

但那套機制裡的判斷邏輯,說穿了只是 is_admin 這一個欄位的二分結果,有管理身分就放行,沒有就全部擋下。這間批發商實際的團隊結構顯然不是這麼一回事。

倉管、業務、財務,這三種人要的後台不一樣

倉管人員每天盯的是商品跟庫存數量對不對,訂單明細裡多寫一件或少算一件,直接影響隔天出貨。理應能編輯商品資料,也理應能新增或刪除訂單明細,因為明細一動,庫存跟著連動,這條線 Day 14:儲存前後可以掛什麼,庫存異動的自動計算 已經接好了。

業務人員在意的是客戶關係跟訂單能不能順利成立。理應能建立與編輯客戶、訂單,但商品的成本、庫存這類數字,不屬於業務的職責範圍,不該讓業務隨手調整,調錯一個成本欄位,影響的可能是整間公司的毛利計算。

財務人員關心的是訂單金額跟客戶往來紀錄是否正確,這份工作性質偏向覆核,理應能檢視訂單細節,卻未必需要新增或編輯訂單本身,那是業務端在跑的流程,財務只需要確認數字對得上。

三種角色,三種完全不同的關注點,如果沿用 Day 15 那個 is_admin 判斷,這三個人只會分成「有管理身分」跟「沒有」兩種待遇,倉管跟業務如果都被設成管理者,會同時看到一模一樣的操作按鈕,這顯然不貼近真實團隊的運作方式。今天的任務不是重新設計 Policy 機制,而是把這三個角色的職責差異,轉換成一張角色對應模組操作的具體矩陣,實作進 Day 15 已經接上的 Policy 類別裡。

先畫矩陣,角色跟模組操作交叉出一張表

矩陣有兩個維度。一個維度是角色,倉管、業務、財務;另一個維度是模組與操作的組合,商品的檢視、編輯,訂單的檢視、建立、編輯,訂單明細的新增、刪除。兩個維度交叉出來的每一格,才是真正要回答的問題,這個角色對這個模組的這個操作,能不能做。

矩陣可以想成一張座位表,角色是縱軸,模組操作是橫軸,每一格才是答案,不能一次把整行或整列填完就交卷,得一格一格想清楚。依前面對三個角色職責的描述,這張批發商的角色權限矩陣大致長這樣:

角色 商品檢視 商品編輯 訂單檢視 訂單建立 訂單編輯 訂單明細新增 訂單明細刪除
倉管 可以 可以 可以 不行 不行 可以 可以
業務 可以 不行 可以 可以 可以 不行 不行
財務 可以 不行 可以 不行 不行 不行 不行

這張表最容易看出「矩陣」這個說法的由來。倉管在商品跟訂單明細上權限很寬,換到訂單本身卻完全不能建立或編輯,同一個角色在不同模組上的權限程度並不一致。反過來看訂單檢視這一欄,三個角色全部都是可以,換到訂單編輯這一欄,只剩業務一個人過關,同一個模組操作,換了角色答案也不一樣。

畫這張表時最容易踩的陷阱,是偷懶把某個角色設計成整行全有或整行全無,反正這個人是管理者就全部放行,反正那個人只是外部窗口就全部擋下。這種懶惰的二分,跟 Day 15 那個 is_admin 判斷本質上是同一個問題,只是換了個名字。畫矩陣真正的目的,正是逼自己把每個角色對每個模組操作都認真想一次,想清楚了才落筆填格子,而不是先看系統有哪些功能,再隨手分配。

把矩陣寫進 Policy,同一個方法依角色回傳不同結果

矩陣畫好,接下來要做的事情很單純,把這張表翻譯成程式碼,寫進 Day 15 已經存在的 Policy 類別裡。原本 OrderPolicy 裡的判斷邏輯是這樣的一個布林值:

public function update(User $user, Order $order): bool
{
    return $user->is_admin;
}

現在要換掉的不是判斷的位置,而是判斷的依據。假設角色資訊存在使用者資料表的 role 欄位,值域是 warehouse、sales、finance 這三種,先從商品 Policy 開始動手,對照矩陣裡商品編輯那一欄,只有倉管可以:

namespace App\Policies;

use App\Models\Product;
use App\Models\User;

class ProductPolicy
{
    public function update(User $user, Product $product): bool
    {
        return $user->role === 'warehouse';
    }
}

update 方法不再是單一個布林值的簡單開關,而是依角色分別判斷。接著處理訂單明細 Policy,對照矩陣裡新增跟刪除這兩欄,同樣只有倉管放行,其他角色一律擋下:

namespace App\Policies;

use App\Models\OrderItem;
use App\Models\User;

class OrderItemPolicy
{
    public function create(User $user): bool
    {
        return $user->role === 'warehouse';
    }

    public function delete(User $user, OrderItem $orderItem): bool
    {
        return $user->role === 'warehouse';
    }
}

訂單 Policy 則要對照矩陣裡建立跟編輯這兩欄,改成只有業務放行:

namespace App\Policies;

use App\Models\Order;
use App\Models\User;

class OrderPolicy
{
    public function create(User $user): bool
    {
        return $user->role === 'sales';
    }

    public function update(User $user, Order $order): bool
    {
        return $user->role === 'sales';
    }
}

寫到這裡會發現一件事,$user->role === 'warehouse' 這一行判斷,在商品 Policy 跟訂單明細 Policy 裡各自出現了一次,內容一模一樣。角色判斷邏輯若在多個 Policy 類別裡重複出現,可以考慮抽出共用的判斷方式,例如在 User Model 上加一個 isWarehouse() 這樣的輔助方法,讓每個 Policy 呼叫 $user->isWarehouse() 而不是各自重寫一次字串比對。這是延續 Day 15 建立的判斷邏輯集中管理精神的自然延伸,集中管理的對象從「要不要放行」,進一步收斂到「怎麼判斷這個角色」。

換上不同角色的帳號登入看看

矩陣寫進 Policy 之後,值不值得信任,不看程式碼,看畫面。準備三組帳號,角色欄位分別設成 warehouse、sales、finance,逐一登入後台檢查。

用倉管帳號登入,打開商品列表,編輯按鈕正常顯示,符合矩陣裡商品編輯那一格。切到某張訂單的訂單明細,新增跟刪除按鈕都在,符合預期。換成業務帳號,同樣打開商品列表,這次編輯按鈕不見了,只剩下檢視。回到訂單列表,建立跟編輯按鈕正常顯示,訂單明細那邊的新增刪除按鈕則跟著消失。最後用財務帳號登入,商品列表看不到編輯按鈕,訂單列表看得到資料卻找不到任何建立或編輯的入口,訂單明細更是連新增按鈕的影子都沒有。

這一刻的意義值得停下來說清楚。這是全系列第一次,同一套後台依登入者身分不同,呈現出真正不同的操作樣貌,不再是所有人打開畫面看到一模一樣的按鈕組合。

倉管看到的是庫存管理視角,業務看到的是接單視角,財務看到的是覆核視角,三種樣貌同時存在同一套 Panel 裡,靠的完全是 Policy 判斷結果的分流,Resource 本身沒有多寫任何一行條件判斷。這正是這個階段一開始設定的目標,設計出貼近真實團隊結構的角色權限,今天算是第一次把它具體兌現。

角色分清楚了,同一個角色到了不同據點呢

倉管、業務、財務這三個角色的權限差異,今天第一次被具體畫成矩陣並實作進 Policy 裡,不同角色登入後看到的操作介面確實不同了。

但這裡有個新的落差得誠實點出來。這張矩陣解決的是「這個角色能不能做這件事」,如果這間批發商同時服務多個經銷據點,兩個同樣是業務角色的人,一個在台北據點、一個在高雄據點,此刻打開訂單列表,看到的仍是完全相同的一份資料,矩陣完全沒有處理「這個角色只能看到自己據點的資料」這一層範圍限制。角色分清楚了,範圍還沒有。

多租戶隔離,讓每個經銷據點只看到自己的資料,是接下來要處理的內容。


上一篇
Day 15:接上 Laravel 既有的 Policy,誰能看見這個操作按鈕
下一篇
Day 17:多租戶隔離,讓每個經銷據點只看到自己的資料
系列文
Laravel Filament 從入門到實戰 共 19 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言