Skip to main content

Command Palette

Search for a command to run...

Lesson 12: 隱形的翻譯官-Middleware 中介層的應用場景

Updated
3 min read

想像今天要進入一個戒備森嚴的皇宮(核心商業邏輯 Controller/Service):

  1. 大門警衛:檢查你有沒有通行證(Authentication)。

  2. 安檢人員:檢查你有沒有攜帶危險物品(Input Validation/Sanitization)。

  3. 禮儀官:看你是哪國人,幫你掛上對應語言的翻譯機(Localization)。

  4. 記錄員:記下幾點幾分誰進去了(Logging)。

這四個角色,就是 Middleware。它們不負責「處理皇宮內的政務」(那是 Controller 的事),它們負責進出皇宮的「預處理」與「後處理」。

在技術上實現,Middleware 是一個洋蔥式(Onion Architecture)的結構:

Request  --> [ Middleware 1 ] --> [ Middleware 2 ] --> (Controller)
                                                            |
Response <-- [ Middleware 1 ] <-- [ Middleware 2 ] <--------+

預處理與後處理的過程

public function handle($request, Closure $next)
{
    // 前處理(Before Controller)

    $response = $next($request);

    // 後處理(After Controller)

    return $response;
}

為什麼我們需要它?

Bad Smell Code

// UserController.php
public function update(UpdateUserRequest $request)
{
    // 1. 檢查有沒有登入 (重複程式碼)
    if (!Auth::check()) {
        return response('Unauthorized', 401);
    }

    // 2. 檢查是不是 JSON 格式 (重複程式碼)
    if (!$request->isJson()) {
        return response('Invalid Content', 400);
    }

    // 3. 記錄 Log (重複程式碼)
    Log::info('User update profile');

    // --- 終於開始做正事 ---
    $user = Auth::user();
    $user->update($request->validated());

    return response()->json($user);
}

Modify

Route::put('/user', [UserController::class , 'update'])
     ->middleware(['auth', 'json.only', 'log.activity']);

// UserController.php
public function update(UpdateUserRequest $request)
{
    // 只剩下純粹的商業邏輯
    $user = Auth::user();
    $user->update($request->validated());

    return response()->json($user);
}

Middleware 的核心概念

六大應用場景

A. 身份驗證與授權 (Authentication & Authorization)

  • 場景:確認 User 是否登入?確認 User 是否有「管理員」權限?

  • 功能:如果是無效的 Token,直接在 Middleware 層擋下並回傳 401 或 403,完全不會觸發 Controller,節省資源。

B. 請求數據清洗 (Data Sanitization/Trimming)

  • 場景:使用者輸入表單時,不小心多按了幾個空白鍵,或者輸入了空的字串。

  • 功能:Laravel 預設就有 TrimStringsConvertEmptyStringsToNull。它像一個濾水器,把髒東西濾掉後,才交給 Controller 處理。

C. 跨來源資源共用 (CORS - Cross-Origin Resource Sharing)

  • 場景:你的前端在 localhost:3000,後端在 localhost:8000,瀏覽器會擋住請求。

  • 功能:Middleware 負責在 Response Header 加上 Access-Control-Allow-Origin: *,這是最典型的「隱形翻譯官」,讓瀏覽器聽得懂後端允許跨域。

D. 流量限制 (Rate Limiting)

  • 場景:防止惡意爬蟲或 DDoS 攻擊,或者 API 收費限制(每分鐘只能 Call 60 次)。

  • 功能:記錄 IP 的請求次數,超過限制直接回傳 HTTP 429 (Too Many Requests)。

E. 語系切換 (Localization)

  • 場景:多語系網站。

  • 功能:Middleware 讀取 Request Header 中的 Accept-Language (例如 zh-TW),然後設定系統當下的 App::setLocale('zh-TW')。這樣 Controller 裡面只需寫 trans('messages.welcome'),就會自動對應到正確語言。

F. 效能監控與日誌 (Logging & Profiling)

  • 場景:想知道每個 API 執行了幾秒?

  • 功能

    1. Request 進來時,記錄時間點 $startTime。

    2. $next($request) 執行後續邏輯。

    3. Response 回來時,計算 microtime() - $startTime

    4. 如果超過 1 秒,寫入 Log 警告。

觀念釐清(ㄧ):Middleware、Policy、Gate

這三個東西做的事情都很像,我們該如何區分呢?

我會利用:「顆粒度 (Granularity)」「是否需要具體的業務資料 (Context Awareness)」

這兩個大方向來進行判斷

  • Middleware (大樓警衛 - 宏觀過濾)

    • 職責:站在大門口。

    • 檢查內容:你有沒有識別證?你的 IP 是否在黑名單?你的 API Token 是不是過期了?

    • 特點:警衛不關心你要去找誰,也不關心你要去幾樓,他只管「能不能讓你進大廳」。

    • 是否依賴資料:否,通常只看 HTTP Header 或 Session。

  • Gate (部門門禁卡 - 權限過濾)

    • 職責:電梯或部門入口。

    • 檢查內容:你是「管理員」嗎?你有「財務部」的權限嗎?

    • 特點:這裡開始區分角色(Role),但通常還不涉及具體的「某一份文件」。

    • 是否依賴資料:通常只依賴 User 這種全域資訊。

  • Policy (保險箱鑰匙 - 微觀/資料過濾)

    • 職責:辦公桌前的最後一道鎖。

    • 檢查內容:你是這份「企劃書」的作者嗎?這張「訂單」是你們部門負責的嗎?

    • 特點:必須拿到**具體的那個物件(Model)**才能判斷。

    • 是否依賴資料:是,高度依賴具體的 Model instance。

為什麼不把 Policy 邏輯寫在 Middleware?

假設你要做一個功能:「使用者只能修改自己的文章」。

如果寫在 Middleware

要在 Middleware 裡做這件事,會遇到兩個大麻煩:

  1. 重複查詢資料庫: Middleware 執行時,Controller 還沒開始跑。為了檢查權限,你必須在 Middleware 裡先 find($id) 一次文章。然後進了 Controller,為了執行業務邏輯,你可能又 find($id) 一次。這是資源浪費。

  2. 解析路由參數很麻煩: Middleware 要知道現在請求的是哪篇文章,必須去解析 URL (/posts/101/edit) 抓出 101。如果路由規則改了,Middleware 的解析邏輯也要跟著改,耦合度太高。

判斷表格

維度Middleware (中介層)Gate (閘門)Policy (策略)
關注點HTTP 請求層級使用者權限/功能層級資料模型 (Model) 層級
典型場景驗證 Token、CORS、Log、鎖 IP、檢查 Content-Type能不能進入後台?能不能看報表?(Yes/No)能不能刪除這筆訂單?只能改自己的資料?
依賴對象Request Header, SessionUser ObjectUser Object + Target Model
執行時機進入 Controller 之前Controller 內或 Blade 內Controller 內 (處理具體資料時)
語義範例"你是合法的使用者嗎?""你有管理員權限嗎?""你是這篇文章的作者嗎?"

觀念釐清(二):Middleware、Request與DTO

會有這個釐清的原因是,上面有提到Middleware有一個職責是:清洗資料。

那我們上一篇文章也剛好說到 DTO 以及 FormRequest,那一樣都是資料處理,這三個又差在哪裡呢?

這其實就是:後端工程師在定義不同工具時,工具個別的職責邊界。

我們可以把這三個角色想像成工廠的流水線

  1. Middleware (清洗):像是「高壓水柱」,不管傳送帶上是什麼原料,先全部沖洗一遍(去掉頭尾空白、空字串轉 Null)。它不關心 這是蘋果還是橘子,它只管「乾淨」。

  2. Form Request (驗證):像是「品管員」。他拿著檢查表,確認這顆「蘋果」有沒有爛掉(Validation),或者把「蘋果」削皮切塊。他很關心這是什麼業務場景。

  3. DTO (傳輸):像是「真空包裝盒」。它不負責清洗也不負責檢查,它只負責把處理好的水果,變成標準的形狀,讓後面的廚師(Service/Domain)好拿、好用。

A. Middleware:全域的、無腦的物理清潔

Middleware (如 Laravel 的 TrimStrings) 是一個全域規則

  • 特點:它不知道 email 欄位是用來幹嘛的,它只知道「凡是字串,前後不該有空白」。

  • 適用場景:通用的 HTTP 協議層級處理。

    • 去除空白 (Trim)

    • 空字串轉 Null

    • HTML標籤過濾 (StripTags - 如果你的網站全站禁止 HTML)

  • 它的盲點:它無法做「特定欄位」的處理(例如:把電話號碼的 拿掉)。

B. Form Request:業務相關的資料預處理

這是在 Controller 之前的業務層級處理。Laravel 的 Form Request 有一個 prepareForValidation() 方法。

  • 特點:它知道現在是在「註冊」,並且知道 phone 欄位需要格式化。

  • 適用場景:特定 API 的資料修整。

    • 0912-345-678 轉成 0912345678

    • price: "1,000" 的逗號拿掉轉成數字。

    • 這裡做的「清洗」,是為了讓後面的 Validation 更順利。

C. DTO (Data Transfer Object):型別安全的資料容器

DTO 通常不應該包含複雜的清洗邏輯,它應該是結果的載體

  • 特點:它假設「進來的資料已經是乾淨且合法的」。

  • 適用場景:將鬆散的 $request->all() 陣列,轉換成有型別提示的物件。

    • $request->input('user_id') (不知是 int 還是 string) 轉變成 public int $userId

    • 確保傳給 Service 的參數永遠是固定的結構。

💡
補充:Laravel如果有用FormRequest來做檢查,可以用validated()來過濾rules中不存在的資料,不用多寫一個DTO。但會有一個情況:假設我們定義了一個SearchRequest,欄位大同小異,但因為某些欄位時有時無,邏輯複雜到不太能用 required_if, required_without, exclude_if 之類的輔助條件,而不得不拆成三個Request;我們就可以嘗試用一個囊括全部的Request,並只做檢查,在利用各自功能的DTO來正規化進到Service的結構內容。

從Rookie到Junior,一個後端成長的30堂課

Part 17 of 31

“從 Rookie 到 Junior:一個後端成長的 30 堂課” 是一套專為後端新手所設計的成長型技術系列文章。內容以實務為導向,逐步拆解後端工程的核心能力,包括程式語言基礎、架構思維、框架運作原理、業務邏輯設計、資料庫操作、以及常見的開發模式。 本系列的目標是協助讀者從零散的學習堆疊,建立成體系的後端知識框架。讀者能夠理解各語言背後不變的工程思維與設計原則。這套內容旨在讓學習者從「會寫程式」進入「能理解系統設計」的階段,逐步具備勝任 Junior Backend Engineer 的能力。

Up next

Lesson 11: 應用層的 Zero Trust-資料驗證 (Validation) 與 DTO

在資安領域有一個非常有名的概念叫做 Zero Trust (零信任)。它的核心精神只有一句話:"Never Trust, Always Verify" (永不信任,始終驗證)。 在網路世界裡,後端工程師的第一條守則是:「永遠不要相信客戶端傳來的輸入」。 過去工程師常認為:「只要使用者登入了(通過驗證),他傳來的資料就是安全的」或者「這是我們自己寫的前端 App 傳來的資料,所以可以信任」。這就是傳統的邊界防禦 (Perimeter Security) 思維——就像城堡,進了城門就沒人管你了。 但...

More from this blog

Lesson 26 : 系統韌性的守護者-限流、熔斷與背壓的設計模式

當這幾個名詞出現後,代表我們進到了一個高併發/大流量的系統了。在這個章節中,我們一起來看看如何透過一些方式來避免高併發導致我們的系統crash掉。 限流(Rate Limiting) 相信大家對這個名詞並不陌生,限流其實就是字面上的含意,限制流量。 限流的目的是保護「接收方」,確保系統不會因為瞬間的高併發請求而癱瘓。 限流通常發生在 API Gateway 或服務的最前端。它像是一個夜店門口的保全

Mar 26, 20262 min read

Lesson 25: 淺談 單體架構、微服務架構與單/多租戶架構

過去我們討論了 要把程式寫在哪、程式要怎麼拆的題目,接下來我們來看看「如何服務不同客戶」,這些架構反映了軟體開發在擴充性與複雜度之間的權衡。 軟體架構深度解析:從系統拆分到商業規模化 在軟體工程的演進中,架構的選擇往往是在「開發效率」、「系統擴充性」與「營運成本」之間尋求平衡。我們可以從兩個核心維度來觀察這些架構:系統如何運行(單體 vs. 分散式) 以及 如何服務客戶(單租戶 vs. 多租戶)。

Mar 26, 20262 min read

Lesson 24: 資料庫擴展術-讀寫分離、複寫機制與快取一致性挑戰

為什麼要讀寫分離? 大多數的 Web 應用都是 「讀多寫少」(例如:看文的人多,發文的人少,Heavy Read System)。當所有的請求都塞給同一台資料庫時,磁碟 I/O 和連線數會成為瓶頸。 Master (主庫): 負責寫入 (Insert/Update/Delete),確保數據一致性。 Slave (從庫): 負責讀取 (Select),可以有多個從庫來分擔讀取壓力。 為什麼讀

Mar 25, 20262 min read

面試經驗談 2025-2026

從2025年3月開始,我陸陸續續參與了從新創到上市櫃公司的Senior - Tech Lead的相關面試,其中有不乏 尊重面試者、展現高度專業的企業(公司),當然也有遇到幾場面試鬼故事,這篇文章主要分享我對於軟體工程師面試的方向分享,以及部分鬼故事,以此警惕自己不要成為這樣的面試官。 AI的洪流,改變了SWE的生態 LLM的發展確確實實的影響到了軟體工程師的生態。 過去受限於算力與資料規模,深度學

Mar 23, 20262 min read

Lesson 23: 系統的緩衝區-Queue 佇列與非同步處理 (Asynchronous)

佇列 佇列的實作工具非常多,舉凡AWS SQS、RabbitMQ、Kafka…等。 佇列的特性,其實是一個非常強大的系統緩衝區,應用層面非常廣。 什麼是佇列? 佇列可以想像成,在既有流程中外,有另一個”水管”,來連接原有的資料流(或邏輯過程),其中 呼叫方將資料 推(Push)到水管中,接受方(監聽) 從水管中將資料拉(Pull)出處理 為什麼佇列是「強大的緩衝區」? 在同步處理中,系統像是一

Mar 23, 20262 min read

Bennett's Tech Blog | 後端架構、系統設計

32 posts

來自台灣的軟體工程師,相信軟體可以改變世界