# 請求流程

本頁說明 `IPGuardian.Check` 對單一請求的判定順序，以及每一步回傳的狀態碼與錯誤訊息。

## 判定順序

```mermaid
graph TB
    A[getDevice] -->|失敗| E500[500 Failed to get device info]
    A --> T{Trust?}
    T -->|是| OK[200]
    T -->|否| B{Block?}
    B -->|是| F1[403 Device is blocked]
    B -->|否| N{Ban?}
    N -->|是| F2[403 Device is banned]
    N -->|否| S[dynamicScore]
    S -->|分數 >= 100| F3[403 Device is blocked]
    S --> RL{速率限制}
    RL -->|超限| F4[403 rate limit]
    RL -->|未超限| OK
```

| 步驟 | 條件 | 結果 |
|---|---|---|
| 1 | 無法解析 IP 或簽發 Session | `500`，`Failed to get device info` |
| 2 | IP 在 Allow 名單 | `200`，跳過後續所有步驟 |
| 3 | IP 處於 Block 期間 | `403`，`Device is blocked, IP: {ip}` |
| 4 | IP 在 Deny 名單 | `403`，`Device is banned, IP: {ip}` |
| 5 | 四維評分總分 `>= 100` | `403`，`Device is blocked, IP: {ip}` |
| 6 | 分數 `>= ScoreDangerous` 且每分鐘請求數 `>= RateLimitDangerous` | `403`，`... rate limit (Dangerous) ...` |
| 7 | 分數 `>= ScoreSuspicious` 且每分鐘請求數 `>= RateLimitSuspicious` | `403`，`... rate limit (Suspicious) ...` |
| 8 | 每分鐘請求數 `>= RateLimitNormal` | `403`，`... rate limit (Normal) ...` |
| 9 | 以上皆未命中 | `200` |

## 每次 Check 都會發生的副作用

`getDevice` 在名單判定之前就執行，因此即使請求最後被拒絕：

| 副作用 | 說明 |
|---|---|
| `frequency:{ip}:{minute}` 加 1 | 每分鐘請求計數；以 Unix 分鐘切窗，不是滑動視窗 |
| 寫入兩個 `Set-Cookie` | `conn.sess.id` 與 `conn.device.id`，每次都刷新有效期 |
| `block:count:{ip}` 加 1 | 只在 IP 處於 Block 期間 |

評分步驟另外會寫入各維度的 Redis 集合與列表，見 [Redis Key](/zh/redis-keys)。

## 速率限制的實際上限

計數包含當前請求且以 `>=` 比較，所以設定值 `N` 代表同一分鐘最多放行 `N - 1` 個請求。以預設值為例：

| 分級 | 設定 | 同一分鐘可通過 |
|---|---|---|
| Normal | `100` | 99 |
| Suspicious | `50` | 49 |
| Dangerous | `20` | 19 |

視窗以 Unix 時間的整分鐘切割，跨分鐘邊界時計數歸零，短時間內最多可通過約兩倍上限。

## 錯誤處理邊界

| 情境 | 行為 |
|---|---|
| Redis 在評分階段失敗 | `dynamicScore` 回傳 `nil` 與錯誤，`Check` 只記錄錯誤後讀取 `score.IsBlock`，造成 nil pointer panic，見 [已知問題](/zh/known-issues) |
| Allow／Deny 查詢 Redis 失敗 | 回退記憶體快取 |
| Block 查詢 Redis 失敗 | 視為未封鎖（fail-open） |
| 每分鐘計數失敗 | 計數視為 `1` |

## 回應格式

`Check` 回傳 `IPGuardianResult`；兩個中介層把失敗結果轉成 JSON：

```json
{"error": "Device is banned, IP: 198.51.100.77"}
```

自行呼叫 `Check` 的寫法見 [中介層](/zh/middleware)。
