利用Cloudflare Worker部署免费的短链接平台
wptr33 2025-07-01 23:41 62 浏览
介绍:
020短链,利用Cloudflare Worker实现的简单免费的短链接平台
部署方法:
第一步:创建命名空间
进入Workers & Pages栏目下的KV项目
创建一个命名空间,点击Create a namespace
记住创建的这个空间名称,一会要用
第二步:创建Worker
进入Workers & Pages栏目下的Overview项目
点击Create application
点击Create Worker
点击deploy
第三步:配置Worker
点击Configure Worker
进入Settings栏目下的Variables项目
绑定KV Namespace
其中Variable name填写LINKS, KV namespace填写你刚刚创建的命名空间名称
在Triggers中修改域名
例如这样:
点击页面上方的Quick edit
填入以下内容
// 项目名,决定html从哪个项目获取,
const github_repo = typeof(GITHUB_REPO)!="undefined" ? GITHUB_REPO
: 'AoEiuV020/Url-Shorten-Worker'
// 项目版本,cdn会有缓存,所以有更新时需要指定版本,
const github_version = typeof(GITHUB_VERSION)!="undefined" ? GITHUB_VERSION
: '@main'
// 密码,密码正确情况无视白名单和超时设置,且支持自定义短链接,
const password = typeof(PASSWORD)!="undefined" ? PASSWORD
: 'AoEiuV020 yes'
// 短链超时,单位毫秒,支持整数乘法,0表示不设置超时,
const shorten_timeout = typeof(SHORTEN_TIMEOUT)!="undefined" ? SHORTEN_TIMEOUT.split("*").reduce((a,b)=>parseInt(a)*parseInt(b),1)
: (1000 * 60 * 10)
// 默认短链key的长度,遇到重复时会自动延长,
const default_len = typeof(DEFAULT_LEN)!="undefined" ? parseInt(DEFAULT_LEN)
: 6
// 为true开启演示,否则无密码且非白名单请求不受理,是则允许访客试用,超时后失效,
const demo_mode = typeof(DEMO_MODE)!="undefined" ? DEMO_MODE === 'true'
: true
// 为true自动删除超时的演示短链接记录,否则仅是标记过期,以便在后台查询历史记录,
const remove_completely = typeof(REMOVE_COMPLETELY)!="undefined" ? REMOVE_COMPLETELY === 'true'
: true
// 白名单中的域名无视超时,json数组格式,写顶级域名就可以,自动通过顶级域名和所有二级域名,
const white_list = JSON.parse(typeof(WHITE_LIST)!="undefined" ? WHITE_LIST
: `[
"aoeiuv020.com",
"aoeiuv020.cn",
"aoeiuv020.cc",
"020.name"
]`)
// 演示模式开启时网页上展示这段禁止滥用提示,并不需要明确表示什么时候失效,
const demo_notice = typeof(DEMO_NOTICE)!="undefined" ? DEMO_NOTICE
: `注意:为防止示例服务被人滥用,故所有由demo网站生成的链接随时可能失效,如需长期使用请自行搭建。`
//console.log(`${github_repo}, ${github_version}, ${password}, ${shorten_timeout}, ${demo_mode}, ${white_list}, ${demo_notice}`)
const html404 = `<!DOCTYPE html>
<body>
<h1>404 Not Found.</h1>
<p>The url you visit is not found.</p>
</body>`
async function randomString(len) {
let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
let maxPos = $chars.length;
let result = '';
for (i = 0; i < len; i++) {
result += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return result;
}
async function checkURL(url){
let str=url;
let Expression=/^http(s)?:\/\/(.*@)?([\w-]+\.)*[\w-]+([_\-.,~!*:#()\w\/?%&=]*)?$/;
let objExp=new RegExp(Expression);
if(objExp.test(str)==true){
if (str[0] == 'h')
return true;
else
return false;
}else{
return false;
}
}
// 检查域名是否在白名单中,参数只包含域名部分,
async function checkWhite(host){
return white_list.some((h) => host == h || host.endsWith('.'+h))
}
async function md5(message) {
const msgUint8 = new TextEncoder().encode(message) // encode as (utf-8) Uint8Array
const hashBuffer = await crypto.subtle.digest('MD5', msgUint8) // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string
return hashHex
}
async function checkHash(url, hash) {
if (!hash) {
return false
}
return (await md5(url+password)) == hash
}
async function save_url(url, key, admin, len) {
len = len || default_len;
// 密码正确且指定了key的情况直接覆盖旧值,
const override = admin && key
if (!override) {
// 密码不正确情况无视指定key,
key = await randomString(len)
}
const is_exists = await load_url(key)
console.log("key exists " + key + " " + is_exists)
if (override || !is_exists) {
var mode = 3
if (admin) {
mode = 0
}
let value = `${mode};${Date.now()};${url}`
if (remove_completely && mode != 0 && !await checkWhite(new URL(url).host)) {
// 利用expirationTtl实现过期记录自动删除,低于60秒会报错,
let ttl = Math.max(60, shorten_timeout / 1000)
console.log("key auto remove: " + key + ", " + ttl + "s")
return await LINKS.put(key, value, {expirationTtl: ttl}),key
} else {
return await LINKS.put(key, value),key
}
} else {
return await save_url(url, key, admin, len + 1)
}
}
async function load_url(key) {
const value = await LINKS.get(key)
if (!value) {
return null
}
const list = value.split(';')
console.log("value split " + list)
var url
if (list.length == 1) {
// 老数据暂且正常跳转,
url = list[0]
} else {
url = list[2]
const mode = parseInt(list[0])
const create_time = parseInt(list[1])
if (mode != 0 && shorten_timeout > 0
&& Date.now() - create_time > shorten_timeout) {
const host = new URL(url).host
if (await checkWhite(host)) {
console.log('white list')
} else {
// 超时和找不到做同样的处理,
console.log("shorten timeout")
return null
}
}
}
return url
}
async function handleRequest(request) {
console.log(request)
if (request.method === "POST") {
let req=await request.json()
console.log("url " + req["url"])
let admin = await checkHash(req["url"], req["hash"])
console.log("admin " + admin)
if(!await checkURL(req["url"]) || (!admin && !demo_mode && !await checkWhite(new URL(req["url"]).host))){
// 非演示模式下,非白名单地址当成地址不合法处理,
return new Response(`{"status":500,"key":": Error: Url illegal."}`, {
headers: {
"content-type": "text/html;charset=UTF-8",
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Methods": "POST",
},
})}
let stat,random_key=await save_url(req["url"], req["key"], admin)
console.log("stat " + stat)
if (typeof(stat) == "undefined"){
return new Response(`{"status":200,"key":"/`+random_key+`"}`, {
headers: {
"content-type": "text/html;charset=UTF-8",
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Methods": "POST",
},
})
}else{
return new Response(`{"status":200,"key":": Error:Reach the KV write limitation."}`, {
headers: {
"content-type": "text/html;charset=UTF-8",
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Methods": "POST",
},
})}
}else if(request.method === "OPTIONS"){
return new Response(``, {
headers: {
"content-type": "text/html;charset=UTF-8",
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Methods": "POST",
},
})
}
const requestURL = new URL(request.url)
const path = requestURL.pathname.split("/")[1]
console.log(path)
if(!path){
const html= await fetch(`https://cdn.jsdelivr.net/gh/${github_repo}${github_version}/index.html`)
const text = (await html.text())
.replaceAll("###GITHUB_REPO###", github_repo)
.replaceAll("###GITHUB_VERSION###", github_version)
.replaceAll("###DEMO_NOTICE###", demo_notice)
return new Response(text, {
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
}
const url = await load_url(path)
if (!url) {
// 找不到或者超时直接404,
console.log('not found')
return new Response(html404, {
headers: {
"content-type": "text/html;charset=UTF-8",
},
status: 404
})
}
return Response.redirect(url, 302)
}
addEventListener("fetch", async event => {
event.respondWith(handleRequest(event.request))
})可以修改如下环境变量,Key均为对应大写:
- 调整超时设置
演示模式生成的短链接超时无法访问,
白名单或者密码正确情况超时设置无效,
修改脚本开头的变量shorten_timeout, 单位毫秒,0表示不设置超时,
- 调整白名单
白名单中的域名设置短链接无视超时,
修改脚本开头的变量white_list, 是个json数组,写顶级域名就可以,自动通过顶级域名和所有二级域名,
- 关闭演示模式
只有演示模式开启才允许访客无密码添加非白名单地址,超时短链接会失效,
修改脚本开头的变量demo_mode,为true开启演示,为false无密码且非白名单请求不受理,
- 自动删除演示记录
针对演示模式开启情况下的超时失效的短链接记录是否自动删除,
修改脚本开头的变量remove_completely,为true自动删除超时的演示短链接记录,否则仅是标记过期,以便在后台查询历史记录,
- 修改密码
网页有个隐藏输入框可以输入密码,
密码正确情况无视白名单和超时设置,且支持自定义短链接,
修改脚本开头的变量password,这个私密信息比较建议直接在环境变量里配置,
- 修改短链长度
短链长度就是随机生成的key也就是短链接的path部分的长度,
长度不够时容易出现重复,遇到重复时会自动延长,
修改脚本开头的变量default_len,
相关地址:
GitHub地址:
https://github.com/AoEiuV020/Url-Shorten-Worker
Demo:https://020.name
相关推荐
- oracle数据导入导出_oracle数据导入导出工具
-
关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...
- 继续学习Python中的while true/break语句
-
上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个else解...
- python continue和break的区别_python中break语句和continue语句的区别
-
python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...
- 简单学Python——关键字6——break和continue
-
Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
-
用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...
- Python 中 break 和 continue 傻傻分不清
-
大家好啊,我是大田。今天分享一下break和continue在代码中的执行效果是什么,进一步区分出二者的区别。一、continue例1:当小明3岁时不打印年龄,其余年龄正常循环打印。可以看...
- python中的流程控制语句:continue、break 和 return使用方法
-
Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...
- L017:continue和break - 教程文案
-
continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...
- 作为前端开发者,你都经历过怎样的面试?
-
已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...
- 面试被问 const 是否不可变?这样回答才显功底
-
作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...
- 2023金九银十必看前端面试题!2w字精品!
-
导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。答案:CSS的盒模型是用于布局和定位元素的概念。它由内容区域...
- 前端面试总结_前端面试题整理
-
记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...
- 由浅入深,66条JavaScript面试知识点(七)
-
作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录由浅入深,66条JavaScript面试知识点(一)由浅入深,66...
- 2024前端面试真题之—VUE篇_前端面试题vue2020及答案
-
添加图片注释,不超过140字(可选)1.vue的生命周期有哪些及每个生命周期做了什么?beforeCreate是newVue()之后触发的第一个钩子,在当前阶段data、methods、com...
- 今年最常见的前端面试题,你会做几道?
-
在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...
- 一周热门
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)
