「這筆資料現在的內容不對,但沒人知道是誰、什麼時候改的。」
這句話是很多系統維運過程裡,遲早會聽到的抱怨。異動稽核(audit trail)要解決的正是這個問題:記錄下「誰、什麼時候、把什麼欄位、從什麼值改成什麼值」。今天要看的是這個系列素材專案怎麼導入稽核套件,以及一個更值得記住的延伸案例——當套件原生只涵蓋一部分情境,怎麼把它擴展到套件本身沒有考慮到的事件。
這個系列的素材專案用了 laravel-auditing 這個套件來處理異動稽核。這類套件的核心運作方式是:Model 實作套件要求的介面、使用套件提供的 trait,套件會在 Model 儲存、更新、刪除的時候自動攔截這些事件,把異動內容(改動前的值、改動後的值、操作者是誰)寫進一張獨立的稽核紀錄表。
在某次 commit 紀錄裡,可以看到「為所有 Model 加入 laravel-auditing 審計功能」這樣的描述——這是一次系統性的改動,把套件要求的介面跟 trait,套用到專案裡幾乎每一個 Model 上。這代表從那次改動之後,任何一筆 News、Page、Banner 之類的資料被新增、修改、刪除,都會自動留下一筆可追溯的紀錄,不需要每個 Model 各自手動實作記錄邏輯。
套件把「Model 異動」這類事件處理得很完整,但這裡有一個容易被忽略的邊界:laravel-auditing 原生設計的稽核對象,是 Model 的新增/修改/刪除這幾種 Eloquent 事件。它天生不會知道「使用者登入了」這種完全不牽涉 Model 資料異動的事件,因為登入本身不是在改動某一筆 Model 的欄位。
如果系統的稽核需求只到「資料被誰改了」,套件的原生能力就已經夠用。但如果需求擴大到「誰在什麼時候登入過系統」這種安全稽核情境,套件原生就沒有現成的機制可以直接套用。
這個系列的素材專案有另一次 commit,描述是「登入事件寫入 laravel-auditing 審計記錄」。這次改動做的事情是:監聽 Laravel 內建的登入事件,在事件觸發時,手動組出一筆符合套件稽核紀錄格式的資料,寫進跟 Model 異動共用的那張稽核紀錄表。
這個做法的巧妙之處在於:它沒有為登入事件另外開一張獨立的資料表、另外做一套查詢介面,而是讓登入事件「假裝」自己也是一筆套件認得的稽核紀錄,寫進同一張表。這樣後台如果已經有一個查看稽核紀錄的介面,不需要額外開發,登入紀錄自然而然也會出現在同一個地方,跟 Model 異動紀錄混在一起依時間排序查看。
用一組對照來看這個設計決策:
❌ 為登入事件另外開一套稽核機制
→ 需要新的資料表、新的查詢介面、新的權限控管,
維運時要分別去兩個地方查「這個人做過什麼」
✅ 監聽登入事件,手動組出套件認得的稽核紀錄格式,
寫進同一張稽核表
→ 沿用既有的稽核紀錄查詢介面跟權限控管,
登入紀錄跟資料異動紀錄可以放在同一個時間軸上查看,
不需要重新造一套機制
這個案例值得抽象成一個更通用的判斷方式:當你需要稽核/記錄某個套件原生不支援的事件類型時,先別急著自己另開一套獨立機制。先看套件本身儲存資料的格式是不是清楚、穩定,能不能透過套件提供的方法(或是直接組出符合格式的資料)手動寫入一筆——如果可以,用這個方式沿用套件既有的查詢介面跟基礎設施,通常比另起爐灶更划算,日後維護的地方也更少一處。
這裡也剛好呼應這個系列前幾天提過的 Listener 機制(監聽登入事件的邏輯,正是用 Laravel 的 Event/Listener 機制實作的)——Listener 負責「監聽到事件之後該做什麼」,這裡的答案就是「組一筆稽核紀錄,寫進既有的稽核表」。
回想你的專案裡,有沒有一個套件的稽核/記錄功能,只涵蓋了部分你關心的事件類型?那些沒被涵蓋到的事件,你會怎麼判斷是該延伸套件既有的資料格式,還是真的需要另開一套獨立機制?
laravel-auditing 這類套件透過 Model trait 自動攔截 CRUD 事件完成這件事明天是這個系列的最後一篇,要回頭看這 29 天走過的四大部分,整理出這個系列真正想傳達的一句話——不靠額外框架特性,光是把 Laravel 原生機制用紮實,能撐起什麼樣的真實系統。