# 地理偵測

本頁說明 GeoLite2 資料庫如何啟用、IP 位置如何快取，以及四項地理異常偵測（含不可能旅行）的條件。

## 啟用條件

| 設定 | 結果 |
|---|---|
| `Filepath.CityDB` 與 `Filepath.CountryDB` 皆空 | 不建立 GeoLite2，地理偵測停用 |
| 兩者皆開啟失敗 | 記錄警告，地理偵測停用 |
| 只有 `CountryDB` 成功 | `calcGeo` 要求 `CityDB` 存在，地理偵測整段略過 |
| `CityDB` 成功 | 啟用；City 查詢失敗時才回退 `CountryDB` |

資料庫需自行從 MaxMind 下載（`GeoLite2-City.mmdb`），本函式庫不負責更新。

## 位置查詢與歷史

| 步驟 | 行為 |
|---|---|
| 內網 IP | 不查資料庫，回傳空位置（國家、城市為空，經緯度 0） |
| 快取 | `geo:ip:{ip}` 保存查詢結果 24 小時 |
| 查不到 | 記錄後跳過，該請求地理分數為 0 |
| 歷史 | `geo:locations:{sid}` 推入 `{ms}:{國家碼}:{城市}:{緯度}:{經度}`，保留最近 10 筆，TTL 24 小時 |

## 四項偵測

| Flag | 條件 | 分數 |
|---|---|---|
| `geo_high_risk` | 歷史中出現 `HighRiskCountry` 列出的國家碼 | `ScoreGeoHighRisk`（30） |
| `geo_hopping` | 最近 1 小時內超過 4 個不同國家 | `ScoreGeoHopping`（15） |
| `geo_frequent_switching` | 最近 1 小時內 `>= 4` 個城市、`>= 5` 筆紀錄，且相鄰紀錄城市切換超過 4 次 | `ScoreGeoFrequentSwitch`（20） |
| `rapid_geo_change` | 最新兩筆相隔不到 1 小時，且推算時速 `> 800` km/h，或 30 分鐘內移動 `> 500` km | `ScoreGeoRapidChange`（25） |

`geo_high_risk` 目前因欄位對應錯誤永遠不會觸發，見 [已知問題](/zh/known-issues)。

## 不可能旅行（Impossible Travel）

距離以 Haversine 公式、地球半徑 6371 km 計算，時速 = 距離 ÷ 時間差。800 km/h 約為民航機巡航速度，正常移動不會超過；VPN 切換節點、代理池輪換則會。

| 例子 | 距離 | 時間差 | 結果 |
|---|---|---|---|
| 台北 → 東京 | 約 2,100 km | 10 分鐘 | 觸發（時速與 30 分鐘規則皆成立） |
| 台北 → 台中 | 約 140 km | 20 分鐘 | 不觸發（420 km/h） |
| 台北 → 東京 | 約 2,100 km | 3 小時 | 不比對（超過 1 小時） |

## 邊界

| 情境 | 行為 |
|---|---|
| 同一 Session 交替使用內網與外網 IP | 內網紀錄的經緯度是 (0, 0)，切到外網時會被算成從大西洋移動數千公里，可能誤觸 `rapid_geo_change` |
| GeoLite2 精度 | 城市級位置可能誤差數十公里；同城市不同 IP 只影響城市切換計數 |
| 除錯輸出 | `calcGeo` 每個請求以標準 `log.Print` 輸出位置字串，見 [已知問題](/zh/known-issues) |
