---
title: "後端互動"
date: "2026-02"
category: "隨筆"
tags: ["隨筆"]
description: "我知道洋蔥模型：請求全部進來之後，出去時也會從這裡走一段。 中介軟體的話，用我的理解，有點像是 Python 裡面的 @ 裝飾器。在所有函式被呼叫之前都會先走一遍這裡..."
source: "https://enriquemark.com/zh-hant/posts/%E5%90%8E%E7%AB%AF%E4%BA%A4%E4%BA%92"
---

我知道洋蔥模型：請求全部進來之後，出去時也會從這裡走一段。

中介軟體的話，用我的理解，有點像是 Python 裡面的 @ 裝飾器。在所有函式被呼叫之前都會先走一遍這裡。如果需要對每一個路由訪問都進行一次的重複操作，那這些操作基本上都要放到中介軟體裡。

### 關於 Token
一般稱為 JWT。這東西主要是給後端用的，比如鑑權。你不能前端每次發請求過來都去查一遍資料庫，那樣效率太低也很麻煩，IO耗損會很大。

所以會有 Token 機制：
1. 前端每次請求都會把 Token 發過來，後端通過它核查登入狀態。
2. 如果驗證沒有問題，使用者就不會被跳轉到登入介面。
3. Token 中通常包含一些基本資訊，比如：
   (a) 使用者 ID
   (b) 許可權資訊
   (c) Token 的過期時間

一般來說，對 Token 的校驗都會放在中介軟體裡，每次在路由跳轉之前先校驗許可權，確認使用者是否有權進入頁面。而另一個東西就是 JSON 的返回資料，這是給前端用的（除非要求前端每次都很麻煩地去解析 Token）。前端會藉助裡面的資料，來決定是否要進行下一步渲染等等。

### 關於密碼的處理
最好是在後端將其轉換為雜湊（Hash），否則會面臨很尷尬的安全問題。如果雜湊是在前端生成的，攻擊者只需要修改一下 JS 程式碼，就可以直接繞過驗證邏輯與資料庫互動並完成登入，這會讓加密行為變得根本沒意義。所以各種安全驗證最好都放在後端（服務端）來處理，因為後端執行在伺服器上，即便有人在瀏覽器前端去下功夫破解，也沒辦法更改後端的邏輯。而後端對前端是基於一個零信任原則的。

### 關於 JWT Token 在前端的儲存
通常有兩個選擇：直接存到 LocalStorage 裡面、把它封裝成 Cookie。與此對應，後端也需要進行相應處理。之所以將 Cookie 單獨劃分出來，主要原因是 Cookie 的安全係數更高。

如果只是把 Token 存在 LocalStorage 中，只要 JS 一行程式碼就能把它全部讀取出來。在這種情況下，它就沒辦法防範 XSS 攻擊，只要有一個網頁端的 JS 程式碼注入，就能把所有的 Token 扒得一乾二淨，而 Cookie 就可以有效避免這個問題。

而且這裡有一個重要的區分：F12 除錯臺確實可以直接獲得 Cookie，但那是我作為瀏覽器使用者的的最高許可權；而另一個則是直接在網頁端執行的 JS 程式碼——這兩者是完全不一樣的。對於網頁端執行的 JS 程式碼來說，設定了 HttpOnly 的 Cookie 它就是拿不到的，但是對於 LocalStorage 講，網頁端執行的 JS 也可以直接拿到，這個差別很大。

### 關於路由地址
這個地方遵循相對路徑的邏輯（包括主路由、子路由）。

比如主路由是 /api，那麼所有 /api 的請求都會被轉發到這裡，然後再根據後面的依賴關係轉發到子路由。例如 /api/xx 就會被轉發到 /xx。

關於子路由的寫法：如果要寫子路由的中介軟體，且該中介軟體已經掛載到子路由上了，路徑直接寫一個\*即可，在子路由的模組裡面，單獨寫其相對地址：

```ts
// dashboard.ts
const dashboardRoute = new Hono()

// 1. 保护这个子路由下的“所有”路径
dashboardRoute.use('/*', jwtMiddleware) 

// 2. 定义具体路径 (注意这里不用写 /dashboard 了，只写相对路径)
dashboardRoute.get('/profile', (c) => c.text('我的资料'))
dashboardRoute.get('/settings', (c) => c.text('设置'))
```

關於這個星\*的使用其實挺有意思的：如果是 \/* 的話，那一般就是類似萬用字元的存在，在這個路徑後面的內容會全部匹配；但如果只有一個星號的話，那指的就是這個路由裡面的全部匹配。這裡的坑在於，子路由這裡一定要帶 /\*。

然後主路由如果只有一個星號的話，那就是主路由之下的全局匹配。如果是在掛載路由組的時候，那一定要把子路由的路徑完整寫下來，這樣才能掛到主路由上，防止錯配：

```ts
// app 是主路由，接子路由的完整路径
app.route('/api', authRoute)
app.route('/api/dashboard', dashboardRoute)
```

### 關於介面生成和除錯
這方面有比較好的庫可以一次性生成文件。介面搞好後，讓AI寫好註釋，然後再接上Swagger這樣的工具裡，就可以自動產出介面文件了。而且可以線上除錯，自動更新，非常好用。
當然還有另一個選擇，就是一步到位，讓 AI 全部處理。確實，這個工具在今天看來是可有可無的，但如果有一些除錯上的需求，加上它可能會比較好。或者說，甚至連除錯的單元測試也可以交給 AI 來寫，這確實也沒問題。
