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))
})
|