System Blog
教程
教程
  • 开源的 Windows 12 网页体验版
  • Hexo
  • SquareX
  • 用电视看 Youtube 油管视频的设置方法
  • 启用Chrome的多线程下载
  • 国外接码平台SMS Activate详细使用教程
  • Live-torrent:磁力链和种子在线搜索播放下载
  • Windows 11
    • Windows11 24H2官方ISO系统镜像下载|跳过TPM硬件检测|本地账号登陆
    • U盘启动盘 l 一键绕过 TPM 限制安装 Win11 l 24H2最新版
  • CloudFlare
    • [网盘][Pages] Cloudflare R2 + Workers搭建在线网盘
    • [博客][Workers]CloudFlare搭建永久免费动态博客,无需服务器,可绑定自己的域名!
    • [博客][Pages] ⚡基于 Cloudflare Pages + Workers + D1 + R2 的动态博客
    • [书签][Workers] Card-Tab 书签卡片式管理
    • [图床][Pages] 利用CloudFlare和Telegraph实现免费托管图片
    • [短链][Pages] Sink Cloudflare系列之短链接生成器
    • [短链][Worker] 利用Cloudflare及KV搭建可自定义路径的短链程序
    • [短链][Workers] 无服务器 自建短链服务 Url-Shorten-Worker 完整的部署
    • [短链][Workers] Cloudflare Workers + Vercel 搭建短链接系统
    • [短链][Pages] Cloudflare Pages 创建的 URL 缩短器
    • [短链][Worker]URL Shortener无需服务器轻松部署,可绑定自定义域名
  • Mail
    • Gmail
    • Outlook
    • Proton
    • Zoho
  • Office
    • 免费 | 安装 | 正版Office全家桶 | 微软官方 LTSC 2024 长期服务版
    • 免费 | 微软官方途径安装Office 2024,包括Word, Excel, PPT。下载途径和激活码均来自官网
  • 将 Telegram Channel 转为微博客/说说/树洞/备忘录/分享
  • NAS OS
    • 飞牛NAS VMware 安装及设置
    • 飞牛NAS通过Cloudflare-Tunnels低成本实现内网穿透
    • 飞牛NAS通过Cpolar 内网穿透,轻松实现无公网远程访问
    • 飞牛NAS安装V2rayA,解决网络问题!
    • 飞牛NAS使用Docker安装OpenWrt/iStoreOS系统详细教程 !
    • Page 1
    • Docker搭建虚拟浏览器(Chrome)(Edge)(Firefox)
  • Docker 项目
    • [推荐]Docker搭建一个网页版办公软件-WPS-Office
    • [推荐]Docker搭建一个适用于个人的在线网盘(列目录)程序-ZFile
    • [推荐]Docker搭建一个轻量的视频分享网站-FireShare
    • [推荐]Docker部署跨平台文件传输工具-PairDrop
    • [推荐]Docker部署TTS文本转语音工具-EasyVoice
    • [推荐]Docker部署免费在线观影平台-LibreTV
    • Docker安装Windows
    • Docker搭建一个火狐浏览器(Firefox)
    • Docker搭建二次验证应用OTP开源程序-2FAuth
    • Docker搭建一个开源的密码管理服务平台-Bitwarden
    • Docker搭建一个免费使用ChatGPT的Lobe-Chat应用
    • Docker搭建一个好用的导航站点-Sun-Panel
    • Docker搭建一个好看,简约的标签页-Mtab
    • Docker搭建一个高颜值的网页版SSH/Telnet客户端-Sshwifty
    • Docker搭建TVADB助手-给电视轻松安装第三方应用
    • Docker搭建一个为开发者提供方便的网页版IT工具箱-IT-Tools
    • Docker搭建一个Web音乐播放站点,支持音乐下载-Musicn
    • Docker部署一款无限听歌,解放小爱音箱-XiaoMusic
    • Docker搭建一款轻松生成AI证件照-HivisionIDPhotos
    • Docker搭建一个免费的网页版PS图片处理工具-Photopea
    • Docker部署一款支持多种直播平台的直播录制工具-BiliLive-Go
    • Docker搭建专注于文件分享的高颜值轻量小工具-PingvinShare
    • Docker搭建一个文件快递柜-Filecodebox
    • Docker搭建一款极简、易于托管的文件分享服务–PicoShare
    • Docker搭建一个好用的网盘-Cloudreve
    • Docker搭建文件浏览器-File Browser
    • Docker部署一款自托管下载工具-MeTuBe
    • Docker搭建一个B站、油管、知乎视频下载服务-AllTube
    • Docker部署一款类似微信朋友圈项目-Moments
    • Docker部署一款轻量、私有部署的多平台云备忘录-Memos
    • Docker部署文件共享工具-FileDrop
    • Docker一个稳定的IPTV服务,随便看-Allinone
    • Docker搭建可道云网盘-Kodbox
    • Docker部署CloudDrive2挂载网盘到本地(飞牛影视和EMBY都能扫)
    • Docker搭建一款网页端办公系统-GodoOS
    • Docker部署AI智能监控安防系统-Frigate
  • Docker搭建Cloudreve私人网盘 支持离线下载 支持域名访问
  • 协作式书签管理器,用于收集、组织和归档网页–Linkwarden
  • 无哈利波特,开启Google
  • Cloudflare-Workers+域名-打造Docker-Hub自用私有镜像仓库
  • Docker 项目仓库
Powered by GitBook
On this page
  • Github项目
  • 部署
  • 创建 KV
  • 部署 Worker
  • 绑定域名
  • 环境变量
  • 正则表达式重定向

Was this helpful?

  1. CloudFlare

[短链][Worker]URL Shortener无需服务器轻松部署,可绑定自定义域名

Previous[短链][Pages] Cloudflare Pages 创建的 URL 缩短器NextMail

Last updated 8 months ago

Was this helpful?

Github项目

这是一个基于 CloudFlare Worker 部署的链接缩短器,无需服务器轻松部署,可绑定自定义域名。

本分支功能特性:

  • 支持 CloudFlare Worker 环境变量配置参数。

  • 支持权限分级,可设置管理员与访客密码(访问路径),可对访客设置权限限制。

  • 支持对未授权用户、访客用户及管理员用户设置不同的主页。

  • 支持配置正则表达式规则。

  • 可在网页中缓存并管理生成的记录。@crazypeace

  • 可下载全部生成的记录并缓存到本地。@crazypeace

  • PWA 特性支持。

  • 其他细节改进。

部署

通过 Cloudflare 部署,如果你的域名托管在 Cloudflare 则可以绑定你的域名。

创建 KV

创建一个 KV Namespace。

部署 Worker

创建一个 Worker。

去 Worker => Worker 名字 => Variables => KV Namespace Bindings 。

其中 Variable name 填写 LINKS, KV namespace 填写你刚刚创建的命名空间。

Variable name
KV namespace

LINKS

填写创建的KV空间名称

点击 Edit Code,复制本项目中的 index.js 的代码到 Cloudflare Worker 。

index.js
const repo_version = typeof(REPO_VERSION) != "undefined" ? REPO_VERSION :
  "@gh-pages"
// Admin user password.
const password_value_admin = typeof(PASSWORD_ADMIN) != "undefined" ? PASSWORD_ADMIN :
  "admin"
// Guest user password.
const password_value = typeof(PASSWORD) != "undefined" ? PASSWORD :
  ""
// Redirect the homepage if it defined.
const index_redirect = typeof(INDEX_REDIRECT) != "undefined" ? INDEX_REDIRECT :
  ""
// The URL of the deployed website.
const url_exclude = typeof(URL_EXCLUDE) != "undefined" ? URL_EXCLUDE :
  "//url-shortner-demo.iou.icu"
// Homepage path for admin user, use the empty value for default theme.
const theme_admin = typeof(THEME_ADMIN) != "undefined" ? THEME_ADMIN :
  ""
// Homepage path for guest user, use the empty value for default theme.
const theme = typeof(THEME) != "undefined" ? THEME :
  ""
const len = typeof(LEN) != "undefined" ? parseInt(LEN) :
  6
// Control the HTTP referrer header, if you want to create an anonymous link that will hide the HTTP Referer header, please set to "true" .
const no_ref = typeof(NO_REF) != "undefined" ? NO_REF :
  "false"
// Allow Cross-origin resource sharing for API requests.
const cors = typeof(CORS) != "undefined" ? CORS :
  "false"
// For all users. If it is true, the same long url will be shorten into the same short url.
const unique_link = typeof(UNIQUE_LINK) != "undefined" ? UNIQUE_LINK :
  "false"
// For guest user only. Allow users to customize the short url.
const custom_link = typeof(CUSTOM_LINK) != "undefined" ? CUSTOM_LINK :
  "true"
// For guest user only.
const len_limit = typeof(LEN_LIMIT) != "undefined" ? parseInt(LEN_LIMIT) :
  3
// Enable the regular expression redirec.
// The regular expression is configured in json format in #regexRedirect key in KV.
// Regex matching has higher priority than path-value matching.
const regex_redirect = typeof(REGEX_REDIRECT) != "undefined" ? REGEX_REDIRECT :
  "false"
const regex_key = "#regexRedirect";

const html404 = `<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>`

let response_header = {
  "content-type": "text/html;charset=UTF-8",
}

if (cors == "true") {
  response_header = {
    "content-type": "text/html;charset=UTF-8",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "POST",
  }
}

async function randomString(len) {
  len = len || 6;
  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 sha512(url) {
  url = new TextEncoder().encode(url)

  const url_digest = await crypto.subtle.digest({
      name: "SHA-512",
    },
    url, // The data you want to hash as an ArrayBuffer
  )
  const hashArray = Array.from(new Uint8Array(url_digest)); // convert buffer to byte array
  const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
  // console.log(hashHex)
  return hashHex
}
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 save_url(URL) {
  let random_key = await randomString()
  let is_exist = await LINKS.get(random_key)
  console.log(is_exist)
  if (is_exist == null)
    return await LINKS.put(random_key, URL), random_key
  else
    save_url(URL)
}
async function is_url_exist(url_sha512) {
  let is_exist = await LINKS.get(url_sha512)
  console.log(is_exist)
  if (is_exist == null) {
    return false
  } else {
    return is_exist
  }
}
async function handleRequest(request) {
  console.log(request)

  if (request.method === "POST") {
    let req = await request.json()

    let req_password = req["password"]
    let user
    if (password_value_admin && req_password === password_value_admin) {
      user = 10; // Admin user
    } else if (!password_value || req_password === password_value) {
      user = 1; // Guest user
    } else {
      user = 0; // Unauthorized user
    }

    let req_cmd = req["cmd"]
    if (req_cmd == "add") {
      let req_url = req["url"]
      let req_keyPhrase = req["keyPhrase"]
      let req_keyPhLen = req_keyPhrase.length

      // console.log("req_url:"+req_url)
      // console.log("req_keyPhrase:"+req_keyPhrase)
      // console.log("req_password:"+req_password)
      // console.log("user:"+user)

      if (user === 0) {
        // Incorrect password.
        return new Response(`{"status":500,"key": "", "error":"Error: Invalid password."}`, {
          headers: response_header,
        })
      }
      if (user === 1) {
        if (!await checkURL(req_url)) {
          return new Response(`{"status":500,"key": "", "error":"Error: URL illegal."}`, {
            headers: response_header,
          })
        }
        if (req_url.indexOf(url_exclude) != -1) {
          return new Response(`{"status":500,"key": "", "error":"Error: URL illegal."}`, {
            headers: response_header,
          })
        }
        // req_keyPhrase containing symbol.
        if (req_keyPhrase && !/^[a-zA-Z0-9]+$/.test(req_keyPhrase)) {
          return new Response(`{"status":500,"key": "", "error":"Error: Custom short URL illegal."}`, {
            headers: response_header,
          })
        }
        if (req_keyPhLen < len_limit && req_keyPhLen > 0) {
          return new Response(`{"status":500,"key": "", "error":"Error: Custom short URL is too short."}`, {
            headers: response_header,
          })
        }
      }

      // Custom short URL existed.
      let stat, random_key
      if (custom_link == "true" && (req_keyPhrase != "")) {
        let is_exist = await LINKS.get(req_keyPhrase)
        if (is_exist != null && user <= 1) {
          return new Response(`{"status":500,"key": "", "error":"Error: Custom short URL is not available."}`, {
            headers: response_header,
          })
        } else {
          random_key = req_keyPhrase
          stat, await LINKS.put(req_keyPhrase, req_url)
        }
      } else if (unique_link == "true") {
        let url_sha512 = await sha512(req_url)
        let url_key = await is_url_exist(url_sha512)
        if (url_key) {
          random_key = url_key
        } else {
          stat,
          random_key = await save_url(req_url);
          if (typeof(stat) == "undefined") {
            // console.log(await LINKS.put(url_sha512,random_key))
          }
        }
      } else {
        stat,
        random_key = await save_url(req_url);
      }
      // console.log(stat)
      if (typeof(stat) == "undefined") {
        return new Response(`{"status":200, "key":"` + random_key + `", "error": ""}`, {
          headers: response_header,
        })
      } else {
        return new Response(`{"status":500, "key": "", "error":"Error: Reach the KV write limitation."}`, {
          headers: response_header,
        })
      }

      // Delete a single KV record.
    } else if (req_cmd == "del") {
      let req_keyPhrase = req["keyPhrase"]

      if (user == 0) {
        return new Response(`{"status":500,"key": "", "error":"Error: Invalid password."}`, {
          headers: response_header,
        })
      }

      await LINKS.delete(req_keyPhrase)
      return new Response(`{"status":200}`, {
        headers: response_header,
      })

      // Load all KV records.
    } else if (req_cmd == "qryall") {
      if (user !== 10) {
        return new Response(`{"status":500, "error":"Error: Invalid password."}`, {
          headers: response_header,
        })
      }
      let keyList = await LINKS.list()
      if (keyList != null) {
        // 初始化返回数据结构 Init the return struct
        let jsonObjectRetrun = JSON.parse(`{"status":200, "error":"", "kvlist": []}`);

        for (var i = 0; i < keyList.keys.length; i++) {
          let item = keyList.keys[i];

          let url = await LINKS.get(item.name);

          let newElement = {
            "key": item.name,
            "value": url
          };
          // 填充要返回的列表 Fill the return list
          jsonObjectRetrun.kvlist.push(newElement);
        }

        return new Response(JSON.stringify(jsonObjectRetrun), {
          headers: response_header,
        })
      } else {
        return new Response(`{"status":500, "error":"Error: Download records failed."}`, {
          headers: response_header,
        })
      }
    }

  } else if (request.method === "OPTIONS") {
    return new Response(``, {
      headers: response_header,
    })
  }

  const requestURL = new URL(request.url)
  const path = requestURL.pathname.split("/")[1]
  const params = requestURL.search;

  console.log(path)
  // Redirect the homepage.
  if (!path && index_redirect) {
    return Response.redirect(index_redirect, 302)
  }

  // Admin user homepage.
  if (password_value_admin && path == password_value_admin) {
    let index = await fetch("https://cdn.jsdelivr.net/gh/Monopink/Url-Shorten-Worker" + repo_version + "/" + theme_admin + "/index.html")
    index = await index.text()
    index = index.replaceAll(/__REPO_VERSION__/gm, repo_version)
    index = index.replaceAll(/__PASSWORD__/gm, path)
    return new Response(index, {
      headers: {
        "content-type": "text/html;charset=UTF-8",
      },
    })
  }

  // Guest user homepage.
  if ((!path && !password_value) || path == password_value) {
    let index = await fetch("https://cdn.jsdelivr.net/gh/Monopink/Url-Shorten-Worker" + repo_version + "/" + theme + "/index.html")
    index = await index.text()
    index = index.replaceAll(/__REPO_VERSION__/gm, repo_version)
    index = index.replaceAll(/__PASSWORD__/gm, path)
    return new Response(index, {
      headers: {
        "content-type": "text/html;charset=UTF-8",
      },
    })
  } else if (!path) {
    return new Response(html404, {
      headers: {
        "content-type": "text/html;charset=UTF-8",
      },
      status: 404
    })
  }

  let location;

  if (regex_redirect == "true") {
    try {
      const regexJson = await LINKS.get(regex_key);
      const regexDict = JSON.parse(regexJson);

      for (const pattern in regexDict) {
        const regex = new RegExp(pattern);
        if (regex.test(path)) {
          location = path.replace(regex, regexDict[pattern]);
          continue;
        }

      }
    } catch(err){
      console.log(err);
    }
  }

  // Not hit by regex.
  if (!location) {
    const value = await LINKS.get(path);
    location = params ? value + params : value;
  }

  // console.log(value)

  if (location) {
    if (no_ref == "true") {
      let no_ref = await fetch("https://Monopink.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 {
      return Response.redirect(location, 302)
    }
  }
  // If request not in kv, return 404
  return new Response(html404, {
    headers: {
      "content-type": "text/html;charset=UTF-8",
    },
    status: 404
  })
}

addEventListener("fetch", async event => {
  event.respondWith(handleRequest(event.request))
})

点击 Deploy。

绑定域名

去 Worker => Worker 名字 => Triggers => Routes 来绑定你自己的域名来访问。

环境变量

去 Worker => Worker 名字 => Variables => Environment Variables 来配置环境变量。

变量名称
值(默认)
说明

REPO_VERSION

@gh-pages

前端页面仓库版本,如果使用 Jsdelivr CDN 地址则可能需要改为 Release tag,否则可能不是最新版本

PASSWORD_ADMIN

admin

管理员用户密码(访问路径),值为空表示无管理员用户

PASSWORD

访客用户密码(访问路径),值为空表示主页

INDEX_REDIRECT

访客用户密码值不为空时,主页跳转的 URL

URL_EXCLUDE

//zdy.ym

排除本机域名,请修改为你的域名

THEME_ADMIN

管理员用户主页路径,如:theme/admin

THEME

访客用户主页

LEN

6

随机生成的短链路径长度

NO_REF

false

控制 HTTP referrer header

CORS

false

允许 API 请求提供跨源资源共享

UNIQUE_LINK

false

为相同的 URL 生成相同的短链

CUSTOM_LINK

true

允许访客用户自定义短链

LEN_LIMIT

3

允许访客用户自定义短链的最小长度

REGEX_REDIRECT

false

开启正则表达式重定向功能

正则表达式重定向

启用正则表达式重定向功能请先将环境变量 EGEX_REDIRECT 设置为 true。

正则表达式以 json 的格式存储在 KV #regexRedirect 键中,像这样:

Key = #regexRedirect
Value = {"^(example.*)": "https://www.iou.icu/$1","^gg\\.(.*)":"https://www.google.com/search?q=$1"}

在运行时会被转为字典,字典的键为正则表达式匹配规则,值为替换规则。

这条记录代表着有两条正则规则:

规则一

查找:^(example.*)

替换:https://www.iou.icu/$1

规则二

查找:^gg\.(.*)

替换:https://www.google.com/search?q=$1

传入的短链接会依次匹配,应用第一个匹配的规则。

例如传入短链接 https://example.com/example-apple,重定向结果为 https://www.iou.icu/example-apple 。

如果是 https://example.com/gg.apple ,将跳转 https://www.google.com/search?q=apple。你会得到一个快捷搜索。

正则表达式优先于短链接,请确保 json 格式正确并做好转义。