一个简单的带统计的短链接Workers
于 发布于分类: 默认
今天配合 AI 写了一个简单的带统计的短链接中间页程序,部署在 Cloudflare Workers 上,配合 KV 存储实现。不仅能实现长链接转短链接,还能记录每一次点击的次数。
简单的说,就是部署在Worker上的,通过KV储存链接和次数,每次访问都会刷新KV的短链接程序。用pico.min.css优化了一些样式的显示。并且做了简单的管理功能。
代码实现
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const path = url.pathname;
if (path === '/admin' || path.startsWith('/admin/')) {
return handleAdmin(request, env);
}
const shortCode = path.slice(1);
if (shortCode && shortCode !== 'favicon.ico' && shortCode !== '') {
return handleRedirect(shortCode, env, ctx);
}
return new Response('Short Link Service is Running.', { status: 200 });
}
};
async function handleRedirect(shortCode, env, ctx) {
const key = `link:${shortCode}`;
const dataStr = await env.SHORT_LINKS.get(key);
if (!dataStr) {
return new Response('链接不存在或已被删除', { status: 404 });
}
const data = JSON.parse(dataStr);
ctx.waitUntil((async () => {
data.visits = (data.visits || 0) + 1;
await env.SHORT_LINKS.put(key, JSON.stringify(data));
})());
return Response.redirect(data.originalUrl, 302);
}
async function handleAdmin(request, env) {
const auth = request.headers.get('Authorization');
if (!auth) {
return new Response('Unauthorized', {
status: 401,
headers: { 'WWW-Authenticate': 'Basic realm="Admin"' }
});
}
const credentials = atob(auth.slice(6)).split(':');
if (credentials[1] !== env.ADMIN_PASSWORD) {
return new Response('密码错误', { status: 403 });
}
const url = new URL(request.url);
if (request.method === 'POST' && url.pathname === '/admin/create') {
const formData = await request.formData();
const originalUrl = formData.get('originalUrl');
let shortCode = formData.get('customCode') || Math.random().toString(36).substring(2, 8);
if (!originalUrl || !originalUrl.startsWith('http')) {
return new Response('URL 格式不正确', { status: 400 });
}
await env.SHORT_LINKS.put(`link:${shortCode}`, JSON.stringify({
originalUrl,
visits: 0,
createdAt: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
}));
return Response.redirect(`${url.origin}/admin`, 303);
}
if (url.pathname === '/admin/delete') {
const code = url.searchParams.get('code');
if (code) await env.SHORT_LINKS.delete(`link:${code}`);
return Response.redirect(`${url.origin}/admin`, 303);
}
const list = await env.SHORT_LINKS.list({ prefix: 'link:' });
const links = [];
for (const key of list.keys) {
const val = await env.SHORT_LINKS.get(key.name);
if (val) {
const data = JSON.parse(val);
links.push({
code: key.name.replace('link:', ''),
...data
});
}
}
return new Response(renderAdminPage(links), {
headers: { 'Content-Type': 'text/html;charset=UTF-8' }
});
}
function renderAdminPage(links) {
const rows = links.map(link => `
<tr>
<td><a href="/${link.code}" target="_blank">/${link.code}</a></td>
<td style="word-break: break-all;"><small>${link.originalUrl}</small></td>
<td><mark>${link.visits}</mark></td>
<td><small>${link.createdAt || '未知'}</small></td>
<td><a href="/admin/delete?code=${link.code}" onclick="return confirm('确定删除吗?')" style="color: #e53935;">删除</a></td>
</tr>
`).join('');
return `
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>短链接管理后台</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/picocss/1.5.13/pico.min.css">
<style>
body { padding: 20px 0; }
.container { max-width: 900px; }
.header { margin-bottom: 40px; text-align: center; }
mark { font-weight: bold; padding: 2px 8px; border-radius: 4px; }
</style>
</head>
<body>
<main class="container">
<div class="header">
<h1>🔗 Short Link Admin</h1>
<p>轻量、高效的 Cloudflare Workers 短链接生成器</p>
</div>
<article>
<header><strong>新建短链接</strong></header>
<form action="/admin/create" method="POST">
<div class="grid">
<input type="url" name="originalUrl" placeholder="粘贴长链接 (https://...)" required>
<input type="text" name="customCode" placeholder="自定义短码 (选填)">
<button type="submit">立即创建</button>
</div>
</form>
</article>
<figure>
<table role="grid">
<thead>
<tr>
<th>短码</th>
<th>原始链接</th>
<th>点击量</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${rows || '<tr><td colspan="5" style="text-align:center;">暂无数据</td></tr>'}
</tbody>
</table>
</figure>
</main>
</body>
</html>
`;
}
注意
- KV空间我命名的是SHORT_LINKS,你可以自己改
- 环境变量需要有ADMIN_PASSWORD用来储存管理密码
- 注意绑定WORKER和KV
- 第一次打开/admin,提示输入账户和密码,只输入密码即可!
- 免费的KV每天的PUT次数好像是有限制的,并且还有很多问题。小玩具,不用太在意。
你可以点击这里跳转本文章: https://link.1tb.site/short-link