●●●~/happy-singing — zsh — 多租戶與隔離
HAPPY SINGING · 多租戶與隔離
一套程式、一個資料庫、很多家店。每一層都在回答同一個問題:「這個請求是哪一家店的?」答案只能來自伺服器,前端說了不算。
● 店家 A 的請求● 店家 B 的請求● DJ(替自己那家店)● 平台營運● 被擋下■ API■ 資料■ Netlify
平台層
只有營運者進得來
站台層
應用層:每店一個站
同一份 Songs 程式碼
代理/身分層
「哪一家店」在這裡
被釘死
API 層
同一支程式,
每個請求帶著店家
資料層
共用一份,
用 store_id 切開
songs-config-console
Google 登入+允許清單
建店、開帳號、指派曲庫、
方案、暫停點歌
寫各站自己的 runtime-config(Netlify Blobs)
店家 A 查歌站
自己的一個查歌站
自己的 Netlify 站+網域
有簽章密鑰 → 能點歌
cookie 只認這個網域
店家 B 查歌站
自己的一個查歌站
自己的 Netlify 站+網域
有簽章密鑰 → 能點歌
cookie 只認這個網域
店家 C 查歌站
只有查歌
自己的 Netlify 站+網域
沒有簽章密鑰
歌單照樣看得到
DJ 點播台(共用)
所有店家同一個站
網址看不出是哪家店
登入:店家代碼+帳號
+密碼
簽章代理 · A 站
x-nf-sign:site_url=A
→ signed_proxy_sites
→ storeId=A
一個站只對到一家店
簽章代理 · B 站
x-nf-sign:site_url=B
→ signed_proxy_sites
→ storeId=B
站和店的配對有外鍵鎖住
✕ 沒有代理
點歌 API 打不到,停在查歌
後台簽章代理
/staff-api → /v1/staff
這條代理不帶店家
店家來自 StaffSession
一個帳號只屬於一家店
Bearer token
不經代理
不帶店家
TenantContext · A
storeId=A(來自簽章)
客人 session 也綁 A
前端傳的 storeId 不採信
TenantContext · B
storeId=B(來自簽章)
客人 session 也綁 B
前端傳的 storeId 不採信
大家共用的規則
每個查詢都要傳店家
沒有「不分店」的查詢
對不上=同一個 not_found
不透露那家店存不存在
測試:兩店兩站互打
TenantContext · DJ
{storeId, staffUserId,
role}
owner/manager/staff
角色不夠就擋
/v1/platform/*
沒有 TenantContext
只能打平台端點
代登入:一次性票券
記在 platform_audit_logs
改 storeId?不理會
PostgreSQL · 只有一份 song_requests(示意)
store_idstatus誰寫的經過哪一層
Aqueued客人A 站代理
Bqueued客人B 站代理
AplayedA 店的 DJ後台代理
BplayedB 店的 DJ後台代理
ArejectedA 店的 DJ後台代理
每一列都有 store_id,不能是空的
跨表關聯用 (store_id, id) 複合外鍵
→ A 店的單接不到 B 店的桌
每個查詢都帶著店家
→ 整合測試用兩店兩站互打驗證
建店/開帳號
不在資料庫的資料
R2 曲庫:照曲庫分
不照店分,可以共用
R2 logos/<storeId>/…
R2 avatars/<storeId>/…
Blobs:每站自己一份
檔案位置由 API 決定,
不收前端給的 key
① 全站開關
ORDER_FEATURE_ENABLED
環境變數;關了每一家都不能點
② 平台開關
platform_settings.ordering_enabled
console 一鍵全部暫停
③ 店家暫停
store_settings.ordering_hold
只停這一家,別家照常
④ 店家狀態+服務期限
不是 active,或期限過了
只擋這一家;客人看到的是 not_found
點歌要四道都開才通(③④ 是每店各自一份)