Cloudflare Worker
它是個 Serverless 的服務
最大的優點就是免費的方案很夠用
而且 Cloudflare 的速度很快
如果是個人 小用量 的短網址需求
並且希望有 客製化 的可能
按照自己的使用習慣做調整
那我這幾個月時間使用的心得是蠻推薦的
整個架構主要會用到 Cloudflare Worker 和 KV
Worker 主要當作處理邏輯
KV 則是扮演類似資料庫的角色
KV = 類似 Redis 的 Key-Value 儲存
適合資料量小,但需要快速讀取的場景
價格
目前自己使用的方案是免費版本
雖然 KV + Worker 免費方案的讀取是到十萬/每日,
但如果未來會有其他 Worker 的用量
就要建議不要抓太緊
不建議用這種短網址去投廣告,如果廣告流量突然暴增,可能會造成免費版本不夠使用
短網址系統架構與流程
短網址系統主要處理兩種請求流程:
API Request 流程
- 客戶端發送 API 請求(如 POST 請求建立新短網址)
- Cloudflare Worker 接收並處理請求
- 驗證 API Token 確保安全性
- 根據請求執行增刪查改操作
- 在 KV 儲存中存取資料
- 返回 JSON 格式的操作結果
短網址 流程
- 用戶訪問短網址(如
/abc
、/abc/x
或 /abc/x/spring_sale
)
- Cloudflare Worker 處理重定向請求
- 從 URL 路徑中提取 ShortCode 、平台資訊和活動資訊
- 在 KV 儲存中查詢對應的目標 URL
- 根據查詢結果決定下一步操作
- 如果存在對應的短網址,則添加 UTM 參數(基於平台和活動)
- 執行 301 重定向到最終目標 URL
程式碼說明
讓我們直接來看完整的 Cloudflare Worker Code 實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
| const SOCIAL_PLATFORMS = { x: { source: 'x', medium: 'social' }, facebook: { source: 'facebook', medium: 'social' }, linkedin: { source: 'linkedin', medium: 'social' }, instagram: { source: 'instagram', medium: 'social' }, tiktok: { source: 'tiktok', medium: 'social' }, threads: { source: 'threads_post', medium: 'social' }, community: { source: 'community', medium: 'social' } };
export default { async fetch(request, env, ctx) { const url = new URL(request.url); try { return url.pathname.startsWith('/api/') ? handleApi(request, url, env) : handleRedirect(request, url, env); } catch (error) { return new Response(`錯誤: ${error.message}`, { status: 500 }); } } };
const CONFIG = { defaultUrl: 'https://www.darrelltw.com/', utmMedium: 'social' };
async function handleApi(request, url, env) { const token = request.headers.get('Authorization')?.split(' ')[1]; if (token !== env.API_TOKEN) { return new Response('未授權', { status: 401 }); } const method = request.method; const path = url.pathname.replace(/^\/api/, ''); if ((method === 'POST' || method === 'PUT') && path === '/urls') { const { key, url } = await request.json(); if (!key || !url) return new Response('缺少必要參數', { status: 400 }); await env.SHORT_URLS.put( `/${key.replace(/^\//, '')}`, url ); return jsonResponse({ success: true }); } if (method === 'GET' && path.startsWith('/urls/')) { const key = `/${path.replace('/urls/', '').replace(/^\//, '')}`; const value = await env.SHORT_URLS.get(key); return value ? new Response(value, { headers: { 'Content-Type': 'text/plain' } }) : new Response('找不到短網址', { status: 404 }); } return new Response('無效的API端點', { status: 404 }); }
async function handleRedirect(request, url, env) { try { const path = url.pathname; let redirectUrl = env.DEFAULT_REDIRECT_URL || CONFIG.defaultUrl; if (path !== '/' && path !== '') { const pathParts = path.split('/').filter(Boolean); const shortCode = pathParts[0]; const platform = pathParts[1]; const campaign = pathParts[2]; if (shortCode) { let data = await env.SHORT_URLS.get(`/${shortCode}`); if (data) { try { const finalUrl = new URL(data); if (platform) { if (SOCIAL_PLATFORMS[platform]) { const { source, medium } = SOCIAL_PLATFORMS[platform]; finalUrl.searchParams.set('utm_source', source); finalUrl.searchParams.set('utm_medium', medium); } else { finalUrl.searchParams.set('utm_source', platform); finalUrl.searchParams.set('utm_medium', CONFIG.utmMedium); } } if (campaign) { finalUrl.searchParams.set('utm_campaign', campaign); } redirectUrl = finalUrl.toString(); } catch (error) { console.error('解析短網址資料錯誤:', error); redirectUrl = env.DEFAULT_REDIRECT_URL || CONFIG.defaultUrl; } } } } return Response.redirect(redirectUrl, 301); } catch (error) { console.error('重定向錯誤:', error); return Response.redirect(env.DEFAULT_REDIRECT_URL || CONFIG.defaultUrl, 301); } }
function jsonResponse(data, status = 200) { return new Response( JSON.stringify(data), { status, headers: { 'Content-Type': 'application/json' } } ); }
|
主要處理邏輯
Worker 的入口點是 fetch
函數,它處理所有請求:
1 2 3 4 5 6 7 8 9 10 11 12
| export default { async fetch(request, env, ctx) { const url = new URL(request.url); try { return url.pathname.startsWith('/api/') ? handleApi(request, url, env) : handleRedirect(request, url, env); } catch (error) { return new Response(`錯誤: ${error.message}`, { status: 500 }); } } };
|
這段 Code 檢查 URL 路徑是否以 /api/
開頭
若是則視為 API 請求並交由 handleApi
處理;否則視為短網址訪問,交由 handleRedirect
進行重定向處理。
同時還定義了一些預設配置。
API 管理功能
API 處理函數提供了管理短網址的接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| async function handleApi(request, url, env) { const token = request.headers.get('Authorization')?.split(' ')[1]; if (token !== env.API_TOKEN) { return new Response('未授權', { status: 401 }); } const method = request.method; const path = url.pathname.replace(/^\/api/, ''); if ((method === 'POST' || method === 'PUT') && path === '/urls') { const { key, url } = await request.json(); if (!key || !url) return new Response('缺少必要參數', { status: 400 }); await env.SHORT_URLS.put( `/${key.replace(/^\//, '')}`, url ); return jsonResponse({ success: true }); } if (method === 'GET' && path.startsWith('/urls/')) { const key = `/${path.replace('/urls/', '').replace(/^\//, '')}`; const value = await env.SHORT_URLS.get(key); return value ? new Response(value, { headers: { 'Content-Type': 'text/plain' } }) : new Response('找不到短網址', { status: 404 }); } return new Response('無效的API端點', { status: 404 }); }
|
API 功能主要包括:
- 驗證授權 Token,確保只有授權用戶能夠管理短網址
- 處理 POST/PUT 請求來創建或更新短網址:
- 接收 key( ShortCode )和 url(目標網址)
- 直接將 URL 字符串存儲在 KV 中,而不是 JSON 格式
- 處理 GET 請求來檢索已存在的短網址信息
- 返回適當的 JSON 響應和狀態碼
短網址重定向功能
重定向處理函數負責將短網址轉換為長網址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| async function handleRedirect(request, url, env) { try { const path = url.pathname; let redirectUrl = env.DEFAULT_REDIRECT_URL || CONFIG.defaultUrl; if (path !== '/' && path !== '') { const pathParts = path.split('/').filter(Boolean); const shortCode = pathParts[0]; const platform = pathParts[1]; const campaign = pathParts[2]; if (shortCode) { let data = await env.SHORT_URLS.get(`/${shortCode}`); if (data) { try { const finalUrl = new URL(data); if (platform) { if (SOCIAL_PLATFORMS[platform]) { const { source, medium } = SOCIAL_PLATFORMS[platform]; finalUrl.searchParams.set('utm_source', source); finalUrl.searchParams.set('utm_medium', medium); } else { finalUrl.searchParams.set('utm_source', platform); finalUrl.searchParams.set('utm_medium', CONFIG.utmMedium); } } if (campaign) { finalUrl.searchParams.set('utm_campaign', campaign); } redirectUrl = finalUrl.toString(); } catch (error) { console.error('解析短網址資料錯誤:', error); redirectUrl = env.DEFAULT_REDIRECT_URL || CONFIG.defaultUrl; } } } } return Response.redirect(redirectUrl, 301); } catch (error) { console.error('重定向錯誤:', error); return Response.redirect(env.DEFAULT_REDIRECT_URL || CONFIG.defaultUrl, 301); } }
|
重定向函數的工作流程:
- 從 URL 路徑解析出三個部分: ShortCode 、Platform 和 Campaign
- 使用
path.split('/').filter(Boolean)
處理,更靈活地支援多段式 URL
- 根據 ShortCode 在 KV 儲存中查找對應的目標 URL
- 使用
new URL(data)
直接將 KV 中的字符串作為 URL 解析
- 根據 Platform 參數,檢查是否在
SOCIAL_PLATFORMS
中預定義:
- 如果有,使用預定義的 source 和 medium 設置 UTM 參數
- 如果沒有,則使用 Platform 名稱作為 source,預設 CONFIG.utmMedium 作為 medium
- 如果有活動參數,則設置 utm_campaign
- 執行 301 redirect 到最終 URL
- 對於任何錯誤情況(包括解析 URL 失敗),直接重定向到預設 URL,確保用戶體驗不中斷
使用案例
現在有三種方式可以使用已創建的短網址:
- 基本訪問:
https://your-worker.domain/blog
- 帶平台參數:
https://your-worker.domain/blog/x
- 帶平台和活動參數:
https://your-worker.domain/blog/x/spring_sale
第二種方式會自動添加 UTM 來源和媒介參數,最終重定向到:
https://example.com/my-very-long-blog-post-url?utm_source=x&utm_medium=social
第三種方式會額外添加活動參數,最終重定向到:
https://example.com/my-very-long-blog-post-url?utm_source=x&utm_medium=social&utm_campaign=spring_sale
這樣可以更精準追蹤不同平台和活動的流量來源與轉換率。
設定步驟
1. 新增 Cloudflare Worker 專案
2. 新增 Worker 程式碼
將前面的 Code 保存到 worker.js
。
3. 設定環境變數
在 Cloudflare Dashboard 設定以下環境變數來自訂短網址服務:
4. 設定 KV
先建立好 KV 空間
建立完成後,回到 Worker 設定
繫結(Binding) 剛剛的 KV 空間,ENV 現在是用 SHORT_URLS
來命名
5. 部署 Worker
直接在 Cloudflare Dashboard 中點擊「Save and Deploy」按鈕部署您的 Worker。
試用
建立短網址
使用 curl 或 Postman 等工具發送 API 請求來創建短網址:
1 2 3 4
| curl -X POST https://your-worker.domain/api/urls \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"key":"blog","url":"https://example.com/my-very-long-blog-post-url"}'
|
成功後會返回:
或是直接在剛剛建立的 Cloudflare KV 直接新增一筆紀錄
在儲存短網址時,URL 直接存儲在 KV 中,這樣可以簡化重定向流程和提高效率。
訪問短網址
現在有三種方式可以使用已創建的短網址:
- 基本訪問:
https://your-worker.domain/blog
- 帶平台參數:
https://your-worker.domain/blog/x
- 帶平台和活動參數:
https://your-worker.domain/blog/x/spring_sale
第二種方式會自動添加 UTM 來源和媒介參數,最終重定向到:
https://example.com/my-very-long-blog-post-url?utm_source=x&utm_medium=social
第三種方式會額外添加活動參數,最終重定向到:
https://example.com/my-very-long-blog-post-url?utm_source=x&utm_medium=social&utm_campaign=spring_sale
這樣可以更精確地追蹤不同平台和活動的流量來源與轉換率。
搭配 n8n 自動化的場景
場景是當新文章 push 到 Github 後會觸發 action
action 會調用 n8n 的 workflow 來處理幾件事情
- 產生短網址
- 產生推文供使用者審核,審核完成會幫忙發到 X 上
其中產生短網址的部分就會直接新增一筆記錄到 KV 中
這樣後續就可以直接使用短網址了
總結
這個簡單的短網址系統可以輕鬆部署在 Cloudflare Worker 上,實現基本的短網址和管理功能。
進階功能可以考慮:
- 訪問統計與分析
- 短網址過期時間設定
- 批量管理工具
- 支援更多 UTM 參數(utm_id、utm_term 等)