第 4 週 · 關聯、登入與佈署
KV、R2、備份
用 KV 記住登入手環,用 R2 把留言匯出成一份備份檔。
正在打開這一關…
學習目標
- 說明 KV 適合什麼、不適合什麼
- 把 session 從記憶體 Map 改成 KV
- 用 R2 存一份 JSON 備份,並知道它不是資料庫本體
D1 適合有欄位、要查詢的帳本。有些資料不是表格。登入手環是「用一串隨機字找出使用者」。備份檔是「一整份 JSON 檔」。這兩種分別用 KV 與 R2。
用生活例子理解
- KV 是一牆有標題的信封。你給標題(鍵),拿走裡面的信(值)。它很快,適合權杖、設定、暫時的旗標。不適合要
JOIN或複雜條件的帳。 - R2 是倉庫,放檔案。圖片、匯出的 JSON、壓縮檔都算。它不是拿來取代
SELECT ... WHERE的。 - 備份是某一刻的影印。帳本仍以 D1 為準。影印放在倉庫,帳本壞掉或你想帶走資料時才拆開。
生活比喻:手環寄物櫃是 KV,一個號碼對一個袋子。倉庫貨架是 R2。客人名單的正式表格仍是 D1。不要把手環寫回留言表的每一列。
KV 可以設定 expirationTtl(秒)。最短 60 秒。七天是 60 * 60 * 24 * 7。過期後 get 得到 null,就當登入失效,回 401。
綁定名字由你決定,程式用 env.SESSIONS、env.BACKUPS。建立資源:
npx wrangler kv namespace create SESSIONS
npx wrangler r2 bucket create guestbook-backups把印出的 id 與桶名寫進設定。本機 wrangler dev 會使用本機模擬,仍要在設定裡有這些綁定,編號可先用練習用的 id。
動手做
大約 20 分鐘。對照 examples/04-guestbook/。
- 在設定加上
kv_namespaces與r2_buckets。 - 註冊並登入。在本機用一筆需要登入的
POST /api/messages確認手環有用。 - 停掉 Wrangler 再啟動,若 KV 是本機持久模擬,手環可能還在;若你把 TTL 設得很短,過期後應變回
401。把觀察寫下來。 - 以登入狀態呼叫案例中的備份路徑(範例用
POST /api/admin/backup,並且只允許已登入的人)。 - 回答:備份 JSON 與 D1 誰才是寫入留言的地方?
案例
{
"kv_namespaces": [
{ "binding": "SESSIONS", "id": "00000000000000000000000000000001" }
],
"r2_buckets": [
{ "binding": "BACKUPS", "bucket_name": "guestbook-backups" }
]
}export async function saveSession(env, token, session) {
await env.SESSIONS.put(token, JSON.stringify(session), {
expirationTtl: 60 * 60 * 24 * 7,
});
}
export async function readSession(env, token) {
if (!token) return null;
const raw = await env.SESSIONS.get(token);
if (!raw) return null;
return JSON.parse(raw);
}
export async function deleteSession(env, token) {
if (token) await env.SESSIONS.delete(token);
}export async function backupMessages(env) {
const { results } = await env.DB.prepare(
`SELECT messages.id, messages.body, users.username, messages.created_at
FROM messages
JOIN users ON users.id = messages.user_id
ORDER BY messages.id`,
).all();
const stamp = new Date().toISOString().replaceAll(":", "-");
const key = `exports/${stamp}.json`;
await env.BACKUPS.put(key, JSON.stringify({ exportedAt: stamp, messages: results }), {
httpMetadata: { contentType: "application/json; charset=utf-8" },
});
return { key, count: results.length };
}還原不是今天的自動魔法。你要另寫一支讀取 R2、再寫回 D1 的程式,而且要先想清楚會不會蓋掉新留言。作業階段先做到「能匯出」就合格。
常見錯誤
- 把所有留言都塞進一個 KV 鍵,再在程式裡過濾。你失去 SQL,也容易寫壞整包。
- 把備份當唯一資料庫,D1 沒有列也能跑。匯出失敗時你會沒有主帳。
- 備份路徑沒有檢查登入,任何人都能把留言拷走。
- KV 的值存了密碼或胡椒。權杖對應的 session 只放
userId與username。
自我檢查
- 手環也可以放在 D1 的一張表。這一課仍選 KV,理由是什麼?
- R2 上的 JSON 與 D1 的列,哪一個是正式帳本?
expirationTtl過了之後,get會得到什麼?