Featured image of post Cloudflare Worker 免费搭建极简短链接生成器

Cloudflare Worker 免费搭建极简短链接生成器

Cloudflare Worker 免费搭建极简短链接生成器

前言

还在用第三方短链接平台?平台随时关停、限额收费、数据不归自己掌控,存在极大风险。 本文使用 Cloudflare Workers + Workers KV 零成本搭建专属短链接服务:

  • ✅ 无需服务器、无需域名备案

  • ✅ 完全免费额度,个人使用不限量

  • ✅ 5 分钟一键部署,自带中文前端页面

  • ✅ 数据全部存储在自己的 Cloudflare KV,隐私可控

  • ✅ 支持自定义过期时间、同源隐藏、安全链接检测

一、前置准备

  1. 注册登录 Cloudflare 账号(邮箱即可免费注册)

  2. 准备可选自定义域名(无域名可先用官方 workers.dev 临时域名)

二、完整部署步骤

步骤 1:创建 Workers KV 命名空间(存储长短链接映射)

  1. Cloudflare 左侧菜单栏:存储和数据库 → Workers KV

  2. 点击「创建命名空间」,名称填写 shortlink(自定义即可)

  3. 点击确定,保存命名空间,后续绑定 Worker 使用

步骤 2:新建 Worker 应用

  1. 左侧菜单:计算 → Workers 和 Pages

  2. 点击「创建应用程序」→「从 Hello World! 开始」

  3. 自定义 Worker 名称(如 short-link-worker),点击「部署」

步骤 3:替换完整 Worker 代码

  1. 进入创建好的 Worker,右上角点击「编辑代码」

  2. 删除编辑器内全部默认代码,复制下方完整代码粘贴

  3. 点击右上角「保存并部署」

  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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
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))
})

步骤 4:绑定 KV 命名空间(关键步骤)

  1. 当前 Worker 页面下滑,找到 绑定 → 添加绑定

  2. 绑定类型选择「KV 命名空间」,填写参数:

字段

填写值

变量名称

LINKS(必须和代码内变量名一致,不可修改)

KV 命名空间

选择第一步创建的 shortlink

  1. 点击保存,系统自动重新部署 Worker

步骤 5:访问测试

部署完成后,页面上方会显示默认域名:xxx.yourname.workers.dev 直接浏览器打开该地址,即可使用短链接生成页面。

步骤 6(可选)绑定自定义域名

  1. Worker 页面顶部「域」→「添加自定义域」

  2. 输入已接入 Cloudflare 解析的域名(如 link.xxx.com

  3. 按提示完成域名校验,等待证书自动下发

  4. 完成后即可使用自己的专属域名作为短链地址

三、功能配置说明(代码顶部 config)

打开代码顶部 config 对象,按需修改参数:

1
2
3
4
5
6
7
const config = {
  no_ref: "off", // on=隐藏跳转来源Referer,off=正常跳转
  cors: "on", // 开启跨域,允许第三方API调用生成短链
  unique_link: true, // true=相同长链接固定生成同一个短码;false=每次生成全新短码
  safe_browsing_api_key: "", // 谷歌安全浏览API密钥,留空关闭风险检测
  expiration_ttl: 0, // 短链有效期,单位秒;0=永久有效,86400=24小时过期
}

四、使用教程

  1. 打开你的短链首页(workers.dev 域名 / 自定义域名)

  2. 在输入框粘贴完整长链接(必须带 http/https

  3. 点击「立即生成」,等待页面返回短链接

  4. 点击「复制链接」一键复制,浏览器访问短链自动跳转原地址

  5. 失效短链访问会返回友好中文 404 页面

五、项目特性

  1. 免服务器免备案:依托 Cloudflare 全球边缘网络,国内可访问

  2. 独立数据存储:所有长短链接映射保存在个人 KV,不会丢失泄露

  3. 内置中文前端:无需额外搭建静态页面,开箱即用

  4. API 跨域支持:可对接其他程序批量生成短链接

  5. 防重复短码:随机 6 位字母数字,自动规避冲突

  6. 自定义有效期:支持定时过期短链接

  7. 隐私跳转:可关闭来源 Referer,保护访问隐私

  8. 恶意网址检测:接入谷歌安全浏览拦截钓鱼 / 病毒网站

六、资源与演示

Cloudflare 官网: https://cloudflare.com/

在线演示站: https://link.cunzhangai.com/

注意:演示站链接 24 小时自动失效,长期使用请自行部署 Worker。

七、常见问题

  1. 访问页面空白 / 报错 检查 KV 绑定变量名是否为 LINKS,大小写严格一致,绑定后重新部署代码。

  2. 短链接 404 短码过期、KV 数据被清空、后缀输入错误;可进入 KV 后台手动管理所有链接。

  3. 自定义域名打不开 确认域名已接入 Cloudflare,DNS 解析正常,SSL 证书已下发。

  4. API 跨域调用失败 保持 config.cors = "on",接口 POST 请求 Content-Type 设置为 application/json。

最后更新于 2026-08-04