本教程将指导您使用Cloudflare Workers和Workers KV搭建一个功能完整的短网址服务。该服务支持创建短链接、自动检测微信浏览器并显示提示,使用KV存储短链映射,免费额度足够个人使用。
功能特点
✅ 支持创建短链接(如 /abc123 跳转到目标URL)
✅ 自动检测是否在微信浏览器中打开
✅ 如果在微信中打开,则显示提示:”请复制到浏览器打开”
✅ 使用 Cloudflare KV 存储映射关系(免费额度足够个人使用)
✅ 提供 Web 界面用于生成短链
✅ 防止重复提交相同长链接(基于 SHA-1 哈希去重)
✅ 阻止用户输入自身短链接(避免循环)
✅ 防止短链重复生成(相同URL生成相同短码)
✅ 友好的404错误页面
✅ 移动端友好的响应式设计
准备工作 第一步:注册Cloudflare账号 访问 Cloudflare官网 注册账号。
第二步:创建Worker
登录Cloudflare控制台
进入Workers & Pages
点击”创建应用程序”
选择”创建Worker”
命名Worker(例如:url-shortener)
点击”部署”
第三步:创建KV命名空间
在Worker详情页面,选择”设置”标签
点击”变量” → “KV命名空间绑定”
点击”添加绑定”
变量名称输入:URL_MAPPINGS
点击”创建新的KV命名空间”
命名空间名称输入:URL_MAPPINGS
点击”保存”
第四步:将KV绑定到Worker
在变量绑定部分,选择刚才创建的URL_MAPPINGS命名空间
点击”保存并部署”
核心代码实现 配置文件 1 2 const SHORT_CODE_LENGTH = 6 ;
工具函数 检测被禁用的UA 1 2 3 4 5 function isBannedUA (ua ) { ua = ua.toLowerCase (); return /micromessenger/i .test (ua) || /weibo/ .test (ua) || /qq\// .test (ua); }
SHA-1哈希生成 1 2 3 4 5 6 7 8 async function sha1 (str ) { const encoder = new TextEncoder (); const data = encoder.encode (str); const hashBuffer = await crypto.subtle .digest ('SHA-1' , data); const hashArray = Array .from (new Uint8Array (hashBuffer)); return hashArray.map (b => b.toString (16 ).padStart (2 , '0' )).join ('' ); }
生成短码 1 2 3 4 5 6 7 8 9 function generateShortCode ( ) { const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ; let result = '' ; for (let i = 0 ; i < SHORT_CODE_LENGTH ; i++) { result += chars.charAt (Math .floor (Math .random () * chars.length )); } return result; }
页面模板 404错误页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 短链接不存在</title > </head > <body > <div class ="container" > <div class ="error-icon" > ❌</div > <h1 > 短链接不存在</h1 > <p > 您访问的短链接已失效或不存在</p > <button class ="btn" onclick ="window.location.href='/'" > 返回主页</button > </div > </body > </html >
微信拦截页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 提示</title > </head > <body > <div class ="card" > <h2 > ⚠️无法在当前应用中打开</h2 > <p > 点击右上角菜单</p > <p > 选择 <span class ="highlight" > "在浏览器中打开"</span > </p > </div > </body > </html >
主页(短链接生成器) 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 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 短链接生成器</title > </head > <body > <div class ="container" > <h1 > 🔗 短链接生成器</h1 > <div class ="card" > <form id ="shorten-form" > <input type ="url" id ="url-input" placeholder ="请输入完整网址(如 https://example.com)" required /> <button type ="submit" > 生成短链接</button > </form > <div class ="result" > </div > </div > <p class ="links" > 友链:...</p > </div > <script > document .addEventListener ('DOMContentLoaded' , () => { const form = document .getElementById ('shorten-form' ); document .addEventListener ('click' , (e ) => { if (e.target .classList .contains ('copy-btn' )) { const shortUrl = e.target .closest ('.result' ).querySelector ('a' ).href ; copyLink (shortUrl); } }); form.addEventListener ('submit' , async (e) => { e.preventDefault (); if (/MicroMessenger/i .test (navigator.userAgent )) { alert ('请在浏览器中打开,不要在微信中使用' ); return ; } }); }); </script > </body > </html >
请求处理器 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 async function handleRequest (request ) { const url = new URL (request.url ); const path = url.pathname ; const userAgent = request.headers .get ('User-Agent' ) || '' ; const inWechat = isBannedUA (userAgent); if (inWechat && (path === '/' || path.startsWith ('/' ))) { return new Response (WECHAT_BLOCK_PAGE , { headers : { 'Content-Type' : 'text/html; charset=utf-8' } }); } const shortCodeMatch = path.match (/^\/([a-zA-Z0-9]{6})$/ ); if (shortCodeMatch) { const shortCode = shortCodeMatch[1 ]; const targetUrl = await LINKS .get (shortCode); if (targetUrl) { return Response .redirect (targetUrl, 302 ); } else { return new Response (NOT_FOUND_PAGE , { headers : { 'Content-Type' : 'text/html; charset=utf-8' } }); } } if (request.method === 'GET' && path === '/' ) { return new Response (homePage (), { headers : { 'Content-Type' : 'text/html; charset=utf-8' } }); } if (request.method === 'POST' && path === '/' ) { try { const formData = await request.formData (); let targetUrl = formData.get ('url' )?.trim (); if (!targetUrl) throw new Error ('请输入网址' ); if (!/^https?:\/\//i .test (targetUrl)) { targetUrl = 'https://' + targetUrl; } const normalizedUrl = targetUrl.replace (/\/$/ , '' ); try { const inputUrl = new URL (normalizedUrl); const currentDomain = new URL (url.origin ); if (inputUrl.hostname === currentDomain.hostname && inputUrl.pathname .length === 7 && inputUrl.pathname [0 ] === '/' && /^[a-zA-Z0-9]{6}$/ .test (inputUrl.pathname .substring (1 ))) { return new Response ('输入的URL是短链接,请输入长链接' , { headers : { 'Content-Type' : 'text/plain; charset=utf-8' } }); } } catch (e) { throw new Error ('网址格式无效' ); } const urlHash = (await sha1 (normalizedUrl)).substring (0 , 12 ); const hashKey = `hash:${urlHash} ` ; const existingCode = await LINKS .get (hashKey); if (existingCode) { return new Response (`${url.origin} /${existingCode} ` , { headers : { 'Content-Type' : 'text/plain; charset=utf-8' } }); } const shortCode = generateShortCode (); const fullShortUrl = `${url.origin} /${shortCode} ` ; await Promise .all ([ LINKS .put (shortCode, normalizedUrl), LINKS .put (hashKey, shortCode) ]); return new Response (fullShortUrl, { headers : { 'Content-Type' : 'text/plain; charset=utf-8' } }); } catch (e) { console .error ('短链生成失败:' , e.message ); return new Response (`❌ ${e.message} ` , { headers : { 'Content-Type' : 'text/plain; charset=utf-8' }, status : 400 }); } } if (path !== '/' && !shortCodeMatch) { return new Response (NOT_FOUND_PAGE , { headers : { 'Content-Type' : 'text/html; charset=utf-8' } }); } }
Worker入口点 1 2 3 4 addEventListener ('fetch' , event => { event.respondWith (handleRequest (event.request )); });
部署和使用 部署步骤
将完整代码复制到Worker编辑器中
点击”保存并部署”
访问您的Worker域名(如:your-worker.workers.dev)
使用方法
访问短网址服务主页
在输入框中输入要缩短的完整URL
点击”生成短链接”按钮
复制生成的短链接(格式:your-domain.com/abc123)
分享短链接,用户访问时会自动跳转到原始URL
功能说明
功能
实现方式
短链生成
POST / 提交 url 参数,返回 https://your-worker.example/abc123
跳转
访问 /abc123 → 302 重定向到原始 URL
微信拦截
检测 UA 中包含 MicroMessenger、weibo、QQ/,返回提示页
防重复
对规范化 URL 计算 SHA-1 哈希前 12 位,存为 hash:xxx,下次直接返回已有短码
防自环
禁止用户提交形如 https://your-worker.example/xxxxxx 的短链接
404 页面
所有无效路径均返回友好提示
安全与隐私说明
本服务 不收集任何用户数据
不使用 Cookie、LocalStorage(除临时复制外)
所有逻辑运行在 Cloudflare 边缘节点
KV 存储仅包含:短码 → 长链接 和 哈希 → 短码 映射
技术特性 1. 短码生成策略
使用6位字符(大小写字母+数字)共62^6≈568亿种组合
相同URL始终生成相同短码,避免重复存储
2. 性能优化
KV存储读写操作快速响应
客户端和服务端双重微信检测
响应式设计适配各种设备
3. 安全性
输入URL验证和规范化
防止短链接循环引用
自动补全HTTPS协议
4. 用户体验
简洁直观的操作界面
一键复制短链接功能
友好的错误提示页面
微信环境下的明确指引
注意事项
免费额度限制 :Cloudflare Workers免费套餐包括:
每天10万次请求
KV存储:100,000次读取/天,1,000次写入/天
存储空间:1GB
域名绑定 :可以将自定义域名绑定到Worker,通过”触发器” → “自定义域”设置
监控和统计 :可以在Cloudflare控制台查看Worker的请求统计和错误日志
备份建议 :定期导出KV中的数据作为备份
故障排除 常见问题
Worker无法访问 :检查Worker是否已部署,网络连接是否正常
短链接不跳转 :检查KV绑定是否正确,短码是否已存储
微信检测失效 :检查User-Agent检测逻辑是否被微信更新影响
生成失败 :检查输入URL格式,确保不是短链接本身
调试方法
使用浏览器开发者工具查看网络请求
查看Worker控制台的错误日志
测试不同的User-Agent模拟不同环境
检查KV命名空间中的数据状态
扩展建议
添加统计功能 :记录每个短链接的访问次数
设置过期时间 :为短链接添加有效期
密码保护 :为敏感链接添加访问密码
批量生成 :支持一次生成多个短链接
API接口 :提供RESTful API供第三方调用
后续可扩展方向
添加 API Key 验证(防止滥用)
支持自定义短码(如 /github)
添加点击统计(需额外 KV 或 D1)
集成密码保护短链
支持批量生成
总结 本教程详细介绍了如何使用Cloudflare Workers和Workers KV搭建一个完整的短网址服务。该方案具有部署简单、成本低廉、性能优越的特点,适合个人和小型项目使用。通过微信检测、重复URL检测、友好的用户界面等功能,提供了良好的用户体验。
这个短网址服务可以满足日常的链接缩短需求,同时具备良好的扩展性,可以根据需要进行功能增强和定制开发。