Skip to content

Latest commit

 

History

History
320 lines (259 loc) · 20.5 KB

File metadata and controls

320 lines (259 loc) · 20.5 KB

DPIP API 對照表

區域綁定的端點 —— 經 DNS 負載平衡的裸主機(api.lb、api.core)一律不使用。 區域由 RegionSelection 狀態決定;多活(multi-active)層級會在其各區域之間容錯切換。

  • 區域: LB = tpe1(台北)、khh1(高雄);Core = tyo1(東京)、 tnn1(台南)。
  • 路徑前綴: 所有路徑都是 /api/...。
  • 層級(Tier): lbApi = LB(多活)、coreApi = Core(多活)、 coreExclusiveApi = 僅 api.core-tnn1、coreStaticExclusive = 僅 static.core-tnn1、legacyApi = 舊 server api-1(逐步淘汰中)。

沒有 lib/api/ 巨石檔:每個端點在其所屬 feature 的 data/(基礎設施則在 core/)裡,各自建成一個輕薄的 datasource,並帶著自己的 ApiTier (core/network/api_region.dart);路徑字串集中於 core/network/api_paths.dart(與 EtagInterceptor 共用,不會漂移)。

第一欄一律寫成 類別.方法。 只寫方法名的版本曾經整段對不上程式碼 —— getWeatherStations、getRainLatest、getTyphoonTrack 這些名字從來不存在, 真正的呼叫是一個參數化的 MeteorSnapshotApi 加上兩個專用類別。帶著類別名, 一次 grep 就能證實或推翻這張表的任何一列。

對時不是 HTTP 端點。 App 的時鐘使用真正的 SNTP (flutter_ntp,UDP/123),對 time.exptech.com.tw(主)/ time.apple.com(備),而非 /ntp HTTP 呼叫 —— 見 core/realtime/ntp_time_source.dart 與 app_time.dart(AppTime.utc / AppTime.utc8)。

多活備援 (multi-active)

類別.方法 路徑 層級 主機(容錯順序 = 選定區域優先)
EarthquakeApi.openEewSse /api/v2/eq/eew?sse=1&compress=1 lbApi api.lb-{tpe1,khh1}.exptech.dev
EarthquakeApi.openTremSse /api/v1/trem/sse?topics=trem.rts.v1[&mode=live] lbApi api.lb-{tpe1,khh1}.exptech.dev
EarthquakeApi.getEewRealtime /api/v2/eq/eew lbApi api.lb-{tpe1,khh1}.exptech.dev
EarthquakeApi.getEewAt /api/v2/eq/eew/{sec} coreApi api.core-{tyo1,tnn1}.exptech.dev
EarthquakeApi.getRtsAt /api/v3/trem/rts/{sec} coreApi api.core-{tyo1,tnn1}.exptech.dev
TremStationRepositoryImpl.refresh /resource/station coreStatic static.core-{tyo1,tnn1}.exptech.dev
EarthquakeApi.getReportList /api/v2/eq/report coreApi api.core-{tyo1,tnn1}.exptech.dev
EarthquakeApi.getReport /api/v2/eq/report/{id} coreApi api.core-{tyo1,tnn1}.exptech.dev

地震報告 list(v2)query: limit/page、sort/order (time|intensity|magnitude|depth × asc|desc)、震度/規模/深度 區間、startTime/endTime 為 YYYY-MM-DD(Asia/Taipei 當日)、可選 city/cityMinInt/cityMaxInt。loc 與經緯度篩選已移除。伺服器會把非正規 query 302 到 canonical(參數字母序、去掉預設值)以利 ETag/快取。 getEewAt / getRtsAt 是歷史回放(時間軸),tier 為 coreApi。 getRtsAt 的 {sec} 必須是 10 位數的 Unix 秒;剛過去的一兩分鐘可能只有一個 區域歸檔好(實測 now−30 s:tnn1 200、tyo1 404),所以 404 是常態而非故障。

測站清單 /resource/station 是 CSV(Content-Type 卻標成 JSON): loc_code,id,lat,lon,floor,code,net,time,work,id 是 RTS 用的 hex 裝置 id、 code 是鄉鎮代碼。每個區域的 nginx 各自產生弱 ETag(同檔不同值),帶 If-None-Match 回 304。App 自己帶驗證器、把最後一份成功的清單連同 ETag 存進 dpip.db 的 trem_station(不是 HTTP 快取,那個會被系統清掉), 開強震監視器時先畫存著的、再問一次伺服器。

即時串流走 SSE(gzip 壓縮),不是輪詢。 EEW:?sse=1 把端點切換成 text/event-stream;再加 &compress=1,payload 會以 event: g 事件送出,其 data: 是 base64 的 gzip(解開後就是純 GET 的同一份 JSON),由應用層在 sse_realtime_source.dart 解壓。getEewRealtime 保留為一次性快照。

RTS 走 TREM 串流 /api/v1/trem/sse?topics=…:一條連線可以帶多個 topic, 每則事件以 topic 為名(event: trem.rts.v1),data: 一律是 base64 的 gzip, 內容是 rts.v1 JSON({ts, stations:{<hex id>:{i, pga?, alert?}}, eq},沒有 v2 的 box/int/I/pgv —— 由前端從 alert 測站推算)。連線一開先送 retry: 與 event: info(列出允許與 denied 的 topic)。不帶 mode=live 時是睡眠模式:只送有測站 alert 的幀;App 只在強震監視器顯示時要 mode=live,換模式時先連新的、收到招呼才斷舊的。不要訂 trem.eew.v1 —— 那是 TREM 自己的 EEW,不是 CWA 的。

傳輸、緩衝與重連都藏在 RealtimeSource seam 後面 —— channel、過期分類器與 生命週期都不變。EEW 是突發型(地震之間靜默 → 存活判定用「連線開著」); RTS live 時是連續型(約 2 Hz → 「最近有事件」),睡眠時靜默就是「平靜」。

沒多活備援 (single host, no failover)

Basemap / OSM / Terrain(全域 static LB,無區域)

Basemap、OSM 詳細街道建築與 terrain 都由 MapLibre 直接抓(app 的 tile bridge 會以 URL 為鍵快取),不經 ApiClient 的區域 failover。OSM 是可選疊圖,只覆蓋 臺灣資料範圍;一般 basemap 始終保留,因此資料範圍外不會變成空白。

用途 路徑 主機
basemap /api/v1/map/tiles/{z}/{x}/{y}.pbf static.lb.exptech.dev
OSM 詳細圖資 /api/v1/map/gsi/{z}/{x}/{y}.pbf static.lb.exptech.dev
terrain /api/v1/map/terrain/{z}/{x}/{y}.png static.lb.exptech.dev

Terrain 是 Mapbox terrain-RGB,MapLibre 原生讀得懂。 每個像素編碼 height = (R·65536 + G·256 + B)/10 − 10000 公尺,正是 MapLibre raster-dem 的 encoding: 'mapbox' —— style 直接以該 encoding 使用原始 PNG,不需要任何 app 端轉換(參照 satellite-tiles-go/web 的底圖處理)。 底圖以 encoding: 'mapbox'、tileSize: 512、bounds: [110, 10, 132, 35] 註冊 raster-dem source,疊半透明 hillshade layer 呈現立體感;bounds 刻意大於真實 DEM bbox,讓 hillshade 邊緣永遠不會在畫面上碰到純背景。

雷達(v2)—— core-tnn1

時間清單是差量編碼的 Unix 秒([baseSec, Δ, …]),在 API 主機上帶 ETag/304; tile 是 WebP,放在 static 主機(由 MapLibre 直接抓取,Cache-Control: max-age=300)。{sec} 就是解出清單後的 10 位數秒,直接使用。

有效觀測範圍不是宣告網格。 宣告的是 115–126.5°E、18–29°N(921 × 881 格, 0.0125°),但實際只有四座雷達測距圓的聯集裁到該網格內才有觀測;其餘是空的。 空白代表「未觀測」而非「無降水」,所以地圖的「顯示掃描範圍」外框畫的是那個聯集, 幾何與推導在 radar_scan_range.dart。

類別.方法 路徑 層級 主機
FrameTileApi.getFrames /api/v2/tiles/radar/list coreExclusiveApi api.core-tnn1.exptech.dev
FrameTileApi.tileUrl /api/v2/tiles/radar/{sec}/{z}/{x}/{y}.webp coreStaticExclusive static.core-tnn1.exptech.dev

衛星雲圖(v2)—— core-tnn1

Himawari-9 AHI 的 XYZ WebP,預設是 Band-13 IR。時間清單是差量編碼的 Unix 秒([baseSec, Δ, …]),在 API 主機上帶 ETag/304;tile 在 static 主機。{sec} 就是解出清單後的 10 分鐘秒,直接使用。

{channel} 與 {style} 是路徑段,不是 query。 {channel} 選頻道或產品 —— 單一頻道用數字(13,也是省略時的預設)、命名產品用名稱(btd_wvirw、 cloudtop…,即 satellite-tiles-go/docs.md 的產品目錄)。清單為該 channel 的 交集(產品需要的頻道缺一就不可渲染,list 只列齊全的時刻)。

{style} 只出現在 tile 路徑:數字頻道可選 normal / jma / bd,命名產品一律 normal(調色盤是產品本身的一部分)。gray 會摺成 normal。推導在 FrameTileApi._satelliteStyle。

App 的圖層選擇器為每個 channel 註冊一個獨立圖層(satellite 保留給 B13,其餘為 satellite-<channel>)。

類別.方法 路徑 層級 主機
FrameTileApi.getFrames /api/v2/tiles/satellite/{channel}/list coreExclusiveApi api.core-tnn1.exptech.dev
FrameTileApi.tileUrl /api/v2/tiles/satellite/{channel}/{style}/{sec}/{z}/{x}/{y}.webp coreStaticExclusive static.core-tnn1.exptech.dev

未來1小時降水預報 QPESUMS(v2)—— core-tnn1

QPESUMS 定量降水預報 XYZ WebP。時間清單是差量編碼的 Unix 毫秒 ([baseMs, Δ, …]);tile 在 static 主機。{ms} 就是解出清單後的 13 位數 毫秒,直接使用(時間軸解析已同時支援秒與毫秒)。

覆蓋範圍是方形,整塊都有資料:441 × 561 格,每格 0.0125° (與雷達同解析度);118.0–123.5125°E、20.0–27.0125°N 是整塊格網的外緣, 不是格心座標。

這不是雷達的有效範圍。預報發布在自己的網格上,雷達的是測距圓聯集,兩者在 方形四角(預報有、圓弧無)與圓弧外凸處(圓弧有、方形無)都不一致。「顯示掃描 範圍」外框因此各畫各的幾何,QPESUMS 的在 qpesums_scan_range.dart。

類別.方法 路徑 層級 主機
FrameTileApi.getFrames /api/v2/tiles/qpesums/list coreExclusiveApi api.core-tnn1.exptech.dev
FrameTileApi.tileUrl /api/v2/tiles/qpesums/{ms}/{z}/{x}/{y}.webp coreStaticExclusive static.core-tnn1.exptech.dev

防災地圖 DPM(v2)—— core-tnn1

MapLibre vector tiles(gzip MVT)+ 點位詳情 JSON。目前有 AED / 無障礙廁所 / 避難所三層;其他類型走同一路徑形狀 /api/v2/tiles/dpm/{layer}/…。Tile 與詳情都 在 static 主機(Cache-Control: max-age=60, must-revalidate + ETag); tile 由 MapLibre 直接抓,詳情經 ApiClient。Source-layer 名 = {layer} (AED 為 aed)。單點有 id(內部 PK,打詳情用,非 aed_id);低 zoom 的 cluster 帶 point_count。

類別.方法 路徑 層級 主機
DisasterMapApi.tileUrl /api/v2/tiles/dpm/{layer}/{z}/{x}/{y}.mvt coreStaticExclusive static.core-tnn1.exptech.dev
DisasterMapApi.getAedDetail /api/v2/tiles/dpm/aed/{id} coreStaticExclusive static.core-tnn1.exptech.dev
DisasterMapApi.getRestroomDetail /api/v2/tiles/dpm/restroom/{id} coreStaticExclusive static.core-tnn1.exptech.dev
DisasterMapApi.getShelterDetail /api/v2/tiles/dpm/shelter/{id} coreStaticExclusive static.core-tnn1.exptech.dev

風場 Wind(v2 / v1)—— core-tnn1

風場 overlay:XYZ WebP 圖層 + 低 zoom 的 .bin 向量風場(WND1 格式, fetchWindBin)。圖層選擇器把 wind 註冊為獨立圖層。

{model} 是路徑段,不是 query,而且一個 frame 定址需要兩個時間。 預報是 「哪一次模式跑」加「預報到哪個時刻」,所以 tile 與 .bin 都以 {cycle}(模式執行時刻)+ {validTime}(預報有效時刻)定址;getFrames 回傳的 不透明 frame id 由 FrameTileApi.windFrameParts 拆成這兩段。{model} 是 gfs / ecmwf。

類別.方法 路徑 層級 主機
FrameTileApi.getFrames /api/v2/tiles/wind/{model}/list coreExclusiveApi api.core-tnn1.exptech.dev
FrameTileApi.tileUrl /api/v2/tiles/wind/{model}/{cycle}/{validTime}/{z}/{x}/{y}.webp coreStaticExclusive static.core-tnn1.exptech.dev
FrameTileApi.fetchWindBin /api/v1/wind/{model}/{cycle}/{validTime}.bin coreStaticExclusive static.core-tnn1.exptech.dev

氣象家族(v5)—— core-tnn1

已自 api-1 的 v2/v3 遷移完成。 四個家族(weather / rain / lightning / typhoon)共用同一組形狀:/api/v5/meteor/{family} 是最新快照、/list 是可用時間 清單、/{sec} 是該時刻的歷史快照且放在 static 主機。舊的 /api/v2/meteor/*、/api/v3/weather/* 在 api-1 上仍然活著,但 App 已不再呼叫。

時間軸與數值皆為差量/哨符編碼,由 core/network/meteor_decode.dart 還原: ts 是 [baseSec, Δ, …],數值序列中的 -99 代表 null(缺值),不是讀數。

weather / rain / lightning 三家共用同一個參數化的 MeteorSnapshotApi (建構時傳入 _base),所以方法名只有五個,不是每家一組。颱風的形狀不同, 自成 MeteorTyphoonApi。

類別.方法 路徑 層級
MeteorSnapshotApi.getStation /api/v5/meteor/{family}/station coreExclusiveApi
MeteorSnapshotApi.getLatest /api/v5/meteor/{family} coreExclusiveApi
MeteorSnapshotApi.getList /api/v5/meteor/{family}/list coreExclusiveApi
MeteorSnapshotApi.getAt /api/v5/meteor/{family}/{sec} coreStaticExclusive
MeteorSnapshotApi.getTrend /api/v5/meteor/{family}/trend/{id}?range=24h|7d coreExclusiveApi
MeteorWeatherApi.getRealtime /api/v5/meteor/weather/realtime/{lat},{lng} coreExclusiveApi
MeteorWeatherApi.getForecast /api/v5/meteor/weather/forecast/{code} coreExclusiveApi
MeteorTyphoonApi.getCyclones /api/v5/meteor/typhoon coreExclusiveApi
MeteorTyphoonApi.getTrack /api/v5/meteor/typhoon/track coreExclusiveApi
MeteorTyphoonApi.getPotential /api/v5/meteor/typhoon/potential coreExclusiveApi
MeteorTyphoonApi.getProbability /api/v5/meteor/typhoon/probability coreExclusiveApi
MeteorTyphoonApi.getWarning /api/v5/meteor/typhoon/warning coreExclusiveApi
MeteorTyphoonApi.getList /api/v5/meteor/typhoon/{kind}/list coreExclusiveApi
MeteorTyphoonApi.getAt /api/v5/meteor/typhoon/{kind}/{sec} coreStaticExclusive

{family} = weather | rain | lightning(station 只有前兩家有; lightning 沒有測站,也沒有 trend)。{kind} = track | potential | probability | warning(TyphoonKind.path,與 enum 名同字)。

颱風多颱:/、/track、/potential、/probability、/warning 一律 { updated, cyclones: [...] };唯一識別是 tdNo(CWA CwaTdNo,未命名 TD 也有)。地圖 overlay 由 client 從 typed payloads 組出(不抓 /geojson)。 /warning 的 CAP 通常一報(cyclones 長度 0–1)。

⚠️ ?range 目前被伺服器忽略。 對 weather 與 rain 的 trend/{id} 實測 (2026-08-02,多個測站):range=7d、7D、week、168h、改用其他參數名、 乃至完全不帶參數,回應一律是 "range":"24h" 且為 24 筆逐時資料。App 送出的參數 是對的,是後端尚未實作 —— 在後端補上之前,「7 天」等同 24 小時。

裝置與通知 —— core-tnn1

類別.方法 路徑 層級
LocationApi.updateDeviceLocation /api/v2/location/{platform}/{token}/{version}/{lat},{lng} coreExclusiveApi
NotifyApi.getNotify /api/v2/notify/{token} coreExclusiveApi
NotifyApi.setNotify /api/v2/notify/{token}/{channel}/{status} coreExclusiveApi

舊 server api-1(逐步淘汰中)

後端會把端點陸續搬到 core-tnn1,這裡會隨之縮減。以下仍只在 api-1 上, 且都已在 App 中實際使用:

類別.方法 路徑 層級 使用處
EventApi.getHistoryList /api/v1/dpip/history/list legacyApi 事件頁(全國)
EventApi.getHistoryRegion /api/v1/dpip/history/{region} legacyApi 事件頁(鄉鎮)
EventApi.getRealtimeList /api/v1/dpip/realtime/list legacyApi 首頁拖盤收起(全國生效中)
EventApi.getRealtimeRegion /api/v1/dpip/realtime/{region} legacyApi 首頁拖盤收起(鄉鎮生效中)

/api/v1/dpip/event/{id} 存在於 api-1,但 App 裡沒有任何方法呼叫它 —— 先前這裡列的 getEvent 並不存在於程式碼中。

外部(第三方,無區域)

走 ApiClient.getAbsolute / postAbsolute:沒有 tier、沒有區域容錯,也不參與 ETag 重新驗證。

類別.方法 URL
ChangelogApi.getReleases https://api.github.com/repos/ExpTechTW/DPIP/releases(ETag;per_page=30)
ChangelogApi.getAvatarBytes https://avatars.githubusercontent.com/…(貢獻者頭像,內容定址故長快取)
RainHourTrendApi.getForecast https://exptech.dingbot.tw/api/weather/rainforecast/{code}({code} = 鄉鎮 3 碼;回應為單 series 信封 {"<系列名>": [{"start": 秒, "rain": [60 × mm]}]};空 series [] = 該小時無雨,卡片隱藏)
ServerStatusApi.getStatus https://status.exptech.dev/api/ds/query(POST,Grafana datasource query;伺服器狀態頁)
CloudflareStatusApi.getComponents https://www.cloudflarestatus.com/api/v2/components.json(Cloudflare 元件狀態)
HasteApi.upload https://haste.exptech.dev/api/pastes(POST,上傳 App 日誌;回應的 key 組成 https://haste.exptech.dev/<key>)
MlIntensityService._download https://exptechtw.github.io/TREM-Lite/models/intensity_ml_v1.onnx,失敗再試 https://cdn.jsdelivr.net/gh/ExpTechTW/TREM-Lite@main/packages/core/static/models/intensity_ml_v1.onnx(ML v1 震度模型,14.7 MB、gzip 傳輸約 2.3 MB)

ML v1 模型目前沒有 ExpTech 主機提供(static.core-{tnn1,tyo1} 都是 404), 所以從 TREM-Lite 發布的位置下載,並以 SHA-256 釘住檔案:jsDelivr 跟著 main,同名重訓的模型會被拒絕。第一次開強震監視器時下載,gzip 壓縮後存在 dpip.db 的 ml_model,之後不再連網。檔案一旦放上 static.core-{region}, 改用 coreStatic 層級即可。

衛星 TLE 目前不打網路。 TleSource 有一條遠端更新路徑(TleFetcher), 但正式碼沒有接線(fetch 為 null),實際只讀打包在 assets/astro/ 的元素集。

curl 可用性(2026-08-02 實測,HTTP 狀態碼)

端點 lb-tpe1 lb-khh1 core-tyo1 core-tnn1 api-1
/api/v2/trem/rts 200 200 404 401 200
/api/v2/eq/eew 200 200 200 200 404
/api/v2/eq/report 404 404 200 200 404
/api/v1/trem/station 404 404 404 404 200
/api/v2/tiles/radar/list 404 404 404 200 404
/api/v5/meteor/weather/station 404 404 404 200 404
/api/v5/meteor/weather/list 404 404 404 200 404
/api/v5/meteor/rain/station 404 404 404 200 404
/api/v5/meteor/rain/list 404 404 404 200 404
/api/v5/meteor/lightning/list 404 404 404 200 404
/api/v5/meteor/typhoon/geojson 404 404 404 200 404
/api/v2/meteor/weather/list(舊) 404 404 404 404 200
/api/v2/meteor/rain/list(舊) 404 404 404 404 200
/api/v2/meteor/lightning/list(舊) 404 404 404 404 200
/api/v2/meteor/typhoon/geojson(舊) 404 404 404 404 200
/api/v1/dpip/history/list 404 404 404 404 200
/api/v1/dpip/realtime/list 404 404 404 404 200
/api/v2/notify/{token} 404 404 404 401 429

v5 氣象家族與其 static 快照(static.core-tnn1 的 /api/v5/meteor/{weather,rain,lightning}/{sec})實測皆為 200。舊的 v2 路徑在 api-1 上仍然存活,所以遷移是新增而非切換 —— 但 App 只走 v5。

雷達 tile 不在這張表裡 —— 它們由 static.core-tnn1.exptech.dev 提供 (/api/v2/tiles/radar/{sec}/{z}/{x}/{y}.webp,image/webp),和上面的時間清單 是不同主機。

只有 lb-tpe1 / lb-khh1 對 ?sse=1 回傳真正的 text/event-stream; core-tyo1 之於 eew?sse=1、api-1 之於 rts?sse=1 都是 HTTP 200 但 application/json(旗標被忽略)。core-tnn1 對兩者都回 401。這就是 SSE 串流固定用 lbApi 的原因。


How the client reaches these — ApiClient, tiers, failover — is in ARCHITECTURE.md § Networking.