CF利用worker部署短域名系统
第一步:创建 KV 命名空间
登录 Cloudflare 后台,左侧找到 存储和数据库 → Workers KV,点击创建命名空间。取个名字,比如 shortlink,点击确定。
第二步:创建 Worker
左侧找到 计算 → Workers和Pages,点击 创建应用程序 → 从Hello World!开始,随便取一个名称,点击→部署
第三步:修改代码
进入刚创建的Workers,右上角→ 编辑代码,替换成下方代码,并保存部署
const config = {
no_ref: "off", // 匿名链接控制:设置为 "on" 可隐藏 HTTP Referer 来源头
cors: "on", // 允许 API 请求的跨域资源共享
unique_link: true, // 唯一链接模式:若为 true,相同的长链接会始终生成同一个短链接后缀
safe_browsing_api_key: "", // 谷歌安全浏览 API 密钥(留空则不开启安全检查)
expiration_ttl: 0, // 短链接过期时间(秒),86400 = 24小时,设置为 0 表示永久有效
}
// ==================== 页面模板:404 未找到 ====================
const html404 = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 未找到 - 短链接系统</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; text-align: center; padding: 80px 20px; background: #f3f4f6; color: #333; }
.card { background: white; max-width: 450px; margin: 0 auto; padding: 40px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.05); }
h1 { font-size: 64px; color: #ef4444; margin: 0 0 10px 0; }
p { font-size: 16px; color: #666; margin-bottom: 30px; }
a { color: #667eea; text-decoration: none; font-weight: bold; }
a:hover { text-decoration: underline; }
.footer { margin-top: 40px; font-size: 13px; color: #999; border-top: 1px solid #eee; padding-top: 20px; }
</style>
</head>
<body>
<div class="card">
<h1>404</h1>
<p>抱歉,您访问的短链接不存在或已过期。</p>
<p><a href="/">返回首页生成新链接</a></p>
<div class="footer">
Powered by Cloudflare Workers | <a href="https://www.cunzhangblog.com" target="_blank">Web3村长博客</a>
</div>
</div>
</body>
</html>`
// ==================== 页面模板:高颜值中文首页 ====================
const htmlIndex = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>极简短链接生成器</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; flex-direction: column; justify-content: space-between; align-items: center; padding: 20px; color: #fff; }
.container { background: rgba(255, 255, 255, 0.96); border-radius: 16px; padding: 40px 30px; width: 100%; max-width: 500px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); text-align: center; color: #333; margin: auto; }
h1 { font-size: 26px; margin-bottom: 10px; color: #2d3748; }
p.subtitle { color: #718096; font-size: 14px; margin-bottom: 30px; }
.input-group { display: flex; flex-direction: column; gap: 15px; }
input[type="url"] { width: 100%; padding: 14px; border: 2px solid #e2e8f0; border-radius: 8px; font-size: 15px; outline: none; transition: border-color 0.2s; }
input[type="url"]:focus { border-color: #667eea; }
button { width: 100%; padding: 14px; background: #667eea; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; transition: background 0.2s; }
button:hover { background: #5a67d8; }
.result-box { margin-top: 25px; padding: 15px; background: #f7fafc; border-radius: 8px; border: 1px dashed #cbd5e0; display: none; word-break: break-all; }
.result-title { font-size: 13px; color: #4a5568; font-weight: bold; margin-bottom: 8px; }
.result-url { font-size: 18px; color: #2b6cb0; margin-bottom: 12px; display: block; word-break: break-all; text-decoration: none; font-weight: 500; }
.copy-btn { padding: 8px 16px; background: #48bb78; color: white; border: none; border-radius: 6px; font-size: 13px; cursor: pointer; transition: background 0.2s; }
.copy-btn:hover { background: #38a169; }
footer { text-align: center; font-size: 13px; color: rgba(255,255,255,0.8); margin-top: 20px; }
footer a { color: #fff; text-decoration: underline; font-weight: 500; }
</style>
</head>
<body>
<div class="container">
<h1>极简短链接生成器</h1>
<p class="subtitle">请输入要缩短的长链接(须包含 http:// 或 https://)</p>
<div class="input-group">
<input type="url" id="longUrl" placeholder="https://example.com" required>
<button onclick="shortenUrl()">立即生成</button>
</div>
<div class="result-box" id="resultBox">
<div class="result-title">🎉 短链接生成成功:</div>
<a href="#" id="shortUrl" target="_blank" class="result-url"></a>
<button class="copy-btn" id="copyBtn" onclick="copyToClipboard()">复制链接</button>
</div>
</div>
<footer>
<p>由 Cloudflare Workers 强力驱动 | <a href="https://www.cunzhangblog.com" target="_blank">Web3村长博客</a></p>
</footer>
<script>
async function shortenUrl() {
const longUrl = document.getElementById('longUrl').value.trim();
if(!longUrl) { alert('请输入有效的网址!'); return; }
if(!longUrl.startsWith('http://') && !longUrl.startsWith('https://')) {
alert('网址格式错误!必须以 http:// 或 https:// 开头');
return;
}
const btn = document.querySelector('button');
btn.disabled = true;
btn.innerText = '生成中...';
try {
const res = await fetch(window.location.pathname, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: longUrl })
});
const data = await res.json();
if(res.ok && data.short_url) {
const finalUrl = window.location.origin + data.short_url;
const shortUrlLink = document.getElementById('shortUrl');
shortUrlLink.href = finalUrl;
shortUrlLink.innerText = finalUrl;
document.getElementById('resultBox').style.display = 'block';
} else {
alert('生成失败: ' + (data.error || '未知错误'));
}
} catch(e) {
alert('网络错误,请稍后重试');
} finally {
btn.disabled = false;
btn.innerText = '立即生成';
}
}
function copyToClipboard() {
const urlText = document.getElementById('shortUrl').innerText;
navigator.clipboard.writeText(urlText).then(() => {
const copyBtn = document.getElementById('copyBtn');
copyBtn.innerText = '复制成功!';
copyBtn.style.background = '#38a169';
setTimeout(() => {
copyBtn.innerText = '复制链接';
copyBtn.style.background = '#48bb78';
}, 2000);
}).catch(err => {
alert('复制失败,请手动选择链接进行复制');
});
}
</script>
</body>
</html>`
let response_header = {
"content-type": "text/html;charset=UTF-8",
}
if (config.cors == "on") {
response_header = {
"content-type": "application/json;charset=UTF-8",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
}
}
// 生成随机短链后缀
async function randomString(len) {
len = len || 6;
let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
let maxPos = $chars.length;
let result = '';
for (let i = 0; i < len; i++) {
result += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return result;
}
// 计算 SHA-512(用于唯一链接匹配)
async function sha512(url) {
url = new TextEncoder().encode(url)
const url_digest = await crypto.subtle.digest({ name: "SHA-512" }, url)
const hashArray = Array.from(new Uint8Array(url_digest));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// 检查 URL 格式
async function checkURL(URL) {
let str = URL;
let Expression = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
let objExp = new RegExp(Expression);
if (objExp.test(str) == true) {
return str[0] == 'h';
} else {
return false;
}
}
function getKvPutOptions() {
const MIN_TTL = 60;
const rawTtl = Number(config.expiration_ttl);
const hasValidTtl = Number.isFinite(rawTtl) && rawTtl >= MIN_TTL;
return hasValidTtl ? { expirationTtl: Math.floor(rawTtl) } : {};
}
// 保存链接到 KV(带递归防冲突)
async function save_url(URL) {
let random_key = await randomString()
let is_exist = await LINKS.get(random_key)
if (is_exist == null) {
await LINKS.put(random_key, URL, getKvPutOptions());
return random_key;
} else {
return save_url(URL);
}
}
async function is_url_exist(url_sha512) {
let is_exist = await LINKS.get(url_sha512)
return is_exist || false;
}
// 谷歌安全浏览检查
async function is_url_safe(url) {
let raw = JSON.stringify({
"client": { "clientId": "Url-Shorten-Worker", "clientVersion": "1.0.7" },
"threatInfo": {
"threatTypes": ["MALWARE", "SOCIAL_ENGINEERING", "POTENTIALLY_HARMFUL_APPLICATION", "UNWANTED_SOFTWARE"],
"platformTypes": ["ANY_PLATFORM"],
"threatEntryTypes": ["URL"],
"threatEntries": [{ "url": url }]
}
});
let requestOptions = { method: 'POST', body: raw, redirect: 'follow' };
try {
let result = await fetch("https://safebrowsing.googleapis.com/v4/threatMatches:find?key=" + config.safe_browsing_api_key, requestOptions)
result = await result.json()
return Object.keys(result).length === 0;
} catch (e) {
return true; // 请求失败时默认放行,防止阻塞跳转
}
}
// 核心请求处理器
async function handleRequest(request) {
// 1. 处理跨域预检请求
if (request.method === "OPTIONS") {
return new Response("", { headers: response_header })
}
// 2. 处理 POST 请求 - 创建短链接
if (request.method === "POST") {
let req = await request.json()
if (!await checkURL(req["url"])) {
return new Response(JSON.stringify({ status: 400, error: "网址格式不正确(必须包含 http:// 或 https://)" }), {
headers: response_header,
status: 400
})
}
let random_key
if (config.unique_link) {
let url_sha512 = await sha512(req["url"])
let url_key = await is_url_exist(url_sha512)
if (url_key) {
random_key = url_key
} else {
random_key = await save_url(req["url"])
await LINKS.put(url_sha512, random_key, getKvPutOptions())
}
} else {
random_key = await save_url(req["url"])
}
return new Response(JSON.stringify({
status: 200,
key: "/" + random_key,
short_url: "/" + random_key
}), { headers: response_header })
}
// 3. 处理 GET 请求 - 访问/跳转短链接
const requestURL = new URL(request.url)
const path = requestURL.pathname.split("/")[1]
const params = requestURL.search
// 如果访问的是根目录,直接展示内置的中文高颜值首页
if (!path) {
return new Response(htmlIndex, {
headers: { "content-type": "text/html;charset=UTF-8" },
})
}
// 从 KV 中获取目标长连接
const value = await LINKS.get(path)
let location = params ? value + params : value
if (location) {
// 安全浏览检查
if (config.safe_browsing_api_key) {
if (!(await is_url_safe(location))) {
let warning_page = await fetch("https://xytom.github.io/Url-Shorten-Worker/safe-browsing.html")
warning_page = await warning_page.text()
warning_page = warning_page.replace(/{Replace}/gm, location)
return new Response(warning_page, { headers: { "content-type": "text/html;charset=UTF-8" } })
}
}
// 是否开启隐藏来源页重定向
if (config.no_ref == "on") {
let no_ref = await fetch("https://xytom.github.io/Url-Shorten-Worker/no-ref.html")
no_ref = await no_ref.text()
no_ref = no_ref.replace(/{Replace}/gm, location)
return new Response(no_ref, { headers: { "content-type": "text/html;charset=UTF-8" } })
} else {
// 正常的 302 秒跳转
return Response.redirect(location, 302)
}
}
// 未找到对应的短链接,返回中文 404
return new Response(html404, {
headers: { "content-type": "text/html;charset=UTF-8" },
status: 404
})
}
addEventListener("fetch", async event => {
event.respondWith(handleRequest(event.request))
})
第四步:绑定 KV 命名空间
在 Worker 页面下方找到 绑定 → 添加绑定 → KV 命名空间,点击添加绑定。
| 字段 | 值 |
|---|---|
| 变量名称 | LINKS |
| KV 命名空间 | 选择刚才创建的命名空间 |
第五步:保存并部署
点击添加绑定后会自动部署。部署成功后即可使用 Cloudflare 分配的 workers.dev 域名访问。
第六步:绑定自定义域名(可选)
在 Worker 页面的域中,点击添加域名,绑定你自己的域名。
功能介绍
部署完成后,打开页面即可使用:
- 输入长链接,点击生成,得到一个短链接
- 复制短链接,浏览器打开自动跳转到原地址
- 支持自定义短链(可进入KV空间里面设置)