Note-51688-1

Token ID: 2

ERC-721 1 Transfers

Metadata

{
  "title": "用cloudflare搭建免费的个人短链接平台",
  "tags": [
    "post",
    "短网址",
    "cloudflare"
  ],
  "summary": "用cloudflare搭建免费的个人短链接平台,生成短链使用",
  "sources": [
    "xlog"
  ],
  "external_urls": [
    "https://river-2420.xlog.app/2022-09-21---Personal-short-chain-services.md"
  ],
  "date_published": "2022-09-21T08:00:03.000Z",
  "content": "---\ntitle: \"用cloudflare搭建免费的个人短链接平台\"\nsubtitle: \"\"\ndate: 2022-09-21T16:00:03+08:00\nlastmod: 2022-09-21T17:00:03+08:00\n\nauthor: \"Kingpo\"\nauthorLink: \"\"\nauthorEmail: \"\"\ndescription: \"用cloudflare搭建免费的个人短链接平台,生成短链使用\"\nkeywords: \n- cloudflare\n- 短链平台搭建\ncomment: true\nweight: 0\n\n\ndraft: false\nfeaturedImagePreview: \"\"\n\ntags:\n- 短网址\n- cloudflare\ncategories:\n- 短网址\n\n\n\ntoc:\n  enable: true\n\nseo:\n  images: []\n\n# See details front matter: /theme-documentation-content/#front-matter\n---\n\n<!--more--> \n\n## 短链接\n短链接又称短网址、短码,意思就是形式上比较短的网址。短链接服务,可以通过将一个普通的冗长的网址缩短生成一个新的较短的网址,便于分享传播。\n\n短链接主要应用场景如下:  \n- 短信发送    \n> 短信里用短链接,可以极大减少字符,现在很多营销短信都是用的短网址。\n- 社群推广 \n> 很多社区或社交网站,会屏蔽长链接。微博字数限制,公众号关键字链接限制等,短网址可以缩短字符,规避掉这些限制。   \n- 微信防屏蔽\n> 微信里有各种屏蔽,用短链接可以避免暴露原有地址关键字,规避屏蔽。\n- 活码\n> 短网址是固定的,可以通过修改原链接达到变更地址的作用,此时不用去修改固定的短网址,短网址就相当于一个中间层。主要用于替换更换链接成本较高的地方,比如生成好的二维码等。\n\n**PS:短链接服务一定要用大厂,一般不至于跑路,稳定性有所保证。**\n\n## 自建短链接平台\n这里利用cloudflare workers提供的服务,免费版本每日100000次请求,对个人使用而言完全足够。\n\n脚本配置基于GitHub开源项目[AoEiuV020/Url-Shorten-Worker](https://github.com/AoEiuV020/Url-Shorten-Worker) \n\n**你需要什么:**\n- 已注册cloudflare账号\n- 自己的域名,越短越好,由cloudflare作解析。  \n\n**操作流程:**\n1. Workers KV中创建一个命名空间,名称随意,比如link。\n\n![](https://s3.bmp.ovh/imgs/2022/09/21/4501767843e6885d.png)\n\n2. Workers创建新服务,服务名称随意,启动器选择http路由器。\n![](https://s3.bmp.ovh/imgs/2022/09/21/2a995b96fdb66cc8.png)\n![](https://s3.bmp.ovh/imgs/2022/09/21/7e2497de3e59f2bb.png)\n3. 进入Workers服务,设置=》变量=》KV命名空间绑定,变量名称大写`LINKS`,值就是那个命名空间名;环境变量里再添加系列变量。变量说明如下:\n> **调整超时设置**   \n演示模式生成的短链接超时无法访问,  \n白名单或者密码正确情况超时设置无效,  \n修改脚本开头的变量shorten_timeout, 单位毫秒,0表示不设置超时    \n\n>**调整白名单**\n白名单中的域名设置短链接无视超时,  \n修改脚本开头的变量white_list, 是个json数组,写顶级域名就可以,自动通过顶级域名和所有二级域名\n\n>**关闭演示模式** \n只有演示模式开启才允许访客无密码添加非白名单地址,超时短链接会失效,  \n修改脚本开头的变量demo_mode,为true开启演示,为false无密码且非白名单请求不受理\n\n> **自动删除演示记录**\n针对演示模式开启情况下的超时失效的短链接记录是否自动删除,  \n修改脚本开头的变量remove_completely,为true自动删除超时的演示短链接记录,否则仅是标记过期,以便在后台查询历史记录\n\n> **修改密码**\n网页有个隐藏输入框可以输入密码,  \n密码正确情况无视白名单和超时设置,且支持自定义短链接,  \n修改脚本开头的变量password,这个私密信息比较建议直接在环境变量里配置\n\n> **修改短链长度**\n短链长度就是随机生成的key也就是短链接的path部分的长度,  \n长度不够时容易出现重复,遇到重复时会自动延长,  \n修改脚本开头的变量default_len\n\n**注意:key均为对应大写**\n\n![](https://s3.bmp.ovh/imgs/2022/09/21/91f857cd7be7e6d1.png)\n\n4. 回到【资源】,快速编辑,复制项目中的`index.js`的代码,即以下代码内容,保存并编译。\n![](https://s3.bmp.ovh/imgs/2022/09/21/1a7aea4ce4f40ac9.png)\n\n```js  \n//index.js源代码\n\n\n// 项目名,决定html从哪个项目获取,\n\nconst github_repo = typeof(GITHUB_REPO)!=\"undefined\" ? GITHUB_REPO\n\n    : 'charlie-king/Url-Shorten-Worker'\n\n// 项目版本,cdn会有缓存,所以有更新时需要指定版本,\n\nconst github_version = typeof(GITHUB_VERSION)!=\"undefined\" ? GITHUB_VERSION\n\n    : '@main'\n\n// 密码,密码正确情况无视白名单和超时设置,且支持自定义短链接,\n\nconst password = typeof(PASSWORD)!=\"undefined\" ? PASSWORD\n\n    : 'AoEiuV020 yes'\n\n// 短链超时,单位毫秒,支持整数乘法,0表示不设置超时,\n\nconst shorten_timeout = typeof(SHORTEN_TIMEOUT)!=\"undefined\" ? SHORTEN_TIMEOUT.split(\"*\").reduce((a,b)=>parseInt(a)*parseInt(b),1)\n\n    : (1000 * 60 * 10)\n\n// 默认短链key的长度,遇到重复时会自动延长,\n\nconst default_len = typeof(DEFAULT_LEN)!=\"undefined\" ? parseInt(DEFAULT_LEN)\n\n    : 6\n\n// 为true开启演示,否则无密码且非白名单请求不受理,是则允许访客试用,超时后失效,\n\nconst demo_mode = typeof(DEMO_MODE)!=\"undefined\" ? DEMO_MODE === 'true'\n\n    : true\n\n// 为true自动删除超时的演示短链接记录,否则仅是标记过期,以便在后台查询历史记录,\n\nconst remove_completely = typeof(REMOVE_COMPLETELY)!=\"undefined\" ? REMOVE_COMPLETELY === 'true'\n\n    : true\n\n// 白名单中的域名无视超时,json数组格式,写顶级域名就可以,自动通过顶级域名和所有二级域名,\n\nconst white_list = JSON.parse(typeof(WHITE_LIST)!=\"undefined\" ? WHITE_LIST\n\n    : `[\n\n\"aoeiuv020.com\",\n\n\"aoeiuv020.cn\",\n\n\"aoeiuv020.cc\",\n\n\"020.name\"\n\n    ]`)\n\n// 演示模式开启时网页上展示这段禁止滥用提示,并不需要明确表示什么时候失效,\n\nconst demo_notice = typeof(DEMO_NOTICE)!=\"undefined\" ? DEMO_NOTICE\n\n    : `注意:为防止示例服务被人滥用,故所有由demo网站生成的链接随时可能失效,如需长期使用请自行搭建。`\n\n//console.log(`${github_repo}, ${github_version}, ${password}, ${shorten_timeout}, ${demo_mode}, ${white_list}, ${demo_notice}`)\n\nconst html404 = `<!DOCTYPE html>\n\n<body>\n\n  <h1>404 Not Found.</h1>\n\n  <p>The url you visit is not found.</p>\n\n</body>`\n\n  \n  \n\nasync function randomString(len) {\n\n    let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';    /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/\n\n    let maxPos = $chars.length;\n\n  let result = '';\n\n  for (i = 0; i < len; i++) {\n\n    result += $chars.charAt(Math.floor(Math.random() * maxPos));\n\n  }\n\n  return result;\n\n}\n\nasync function checkURL(url){\n\n    let str=url;\n\n    let Expression=/^http(s)?:\\/\\/(.*@)?([\\w-]+\\.)*[\\w-]+([_\\-.,~!*:#()\\w\\/?%&=]*)?$/;\n\n    let objExp=new RegExp(Expression);\n\n    if(objExp.test(str)==true){\n\n      if (str[0] == 'h')\n\n        return true;\n\n      else\n\n        return false;\n\n    }else{\n\n        return false;\n\n    }\n\n}\n\n// 检查域名是否在白名单中,参数只包含域名部分,\n\nasync function checkWhite(host){\n\n    return white_list.some((h) => host == h || host.endsWith('.'+h))\n\n}\n\nasync function md5(message) {\n\n  const msgUint8 = new TextEncoder().encode(message) // encode as (utf-8) Uint8Array\n\n  const hashBuffer = await crypto.subtle.digest('MD5', msgUint8) // hash the message\n\n  const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array\n\n  const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string\n\n  \n\n  return hashHex\n\n}\n\nasync function checkHash(url, hash) {\n\n    if (!hash) {\n\n        return false\n\n    }\n\n    return (await md5(url+password)) == hash\n\n}\n\nasync function save_url(url, key, admin, len) {\n\n  len = len || default_len;\n\n    // 密码正确且指定了key的情况直接覆盖旧值,\n\n    const override = admin && key\n\n    if (!override) {\n\n        // 密码不正确情况无视指定key,\n\n        key = await randomString(len)\n\n    }\n\n    const is_exists = await load_url(key)\n\n    console.log(\"key exists \" + key + \" \" + is_exists)\n\n    if (override || !is_exists) {\n\n        var mode = 3\n\n        if (admin) {\n\n            mode = 0\n\n        }\n\n        let value = `${mode};${Date.now()};${url}`\n\n        if (remove_completely && mode != 0 && !await checkWhite(new URL(url).host)) {\n\n          // 利用expirationTtl实现过期记录自动删除,低于60秒会报错,\n\n          let ttl = Math.max(60, shorten_timeout / 1000)\n\n          console.log(\"key auto remove: \" + key + \", \" + ttl + \"s\")\n\n          return await LINKS.put(key, value, {expirationTtl: ttl}),key\n\n        } else {\n\n          return await LINKS.put(key, value),key\n\n        }\n\n    } else {\n\n        return await save_url(url, key, admin, len + 1)\n\n    }\n\n}\n\nasync function load_url(key) {\n\n    const value = await LINKS.get(key)\n\n    if (!value) {\n\n        return null\n\n    }\n\n    const list = value.split(';')\n\n    console.log(\"value split \" + list)\n\n    var url\n\n    if (list.length == 1) {\n\n        // 老数据暂且正常跳转,\n\n        url = list[0]\n\n    } else {\n\n        url = list[2]\n\n        const mode = parseInt(list[0])\n\n        const create_time = parseInt(list[1])\n\n        if (mode != 0 && shorten_timeout > 0\n\n            && Date.now() - create_time > shorten_timeout) {\n\n            const host = new URL(url).host\n\n            if (await checkWhite(host)) {\n\n                console.log('white list')\n\n            } else {\n\n                // 超时和找不到做同样的处理,\n\n                console.log(\"shorten timeout\")\n\n                return null\n\n            }\n\n        }\n\n    }\n\n    return url\n\n}\n\nasync function handleRequest(request) {\n\n  console.log(request)\n\n  if (request.method === \"POST\") {\n\n    let req=await request.json()\n\n    console.log(\"url \" + req[\"url\"])\n\n    let admin = await checkHash(req[\"url\"], req[\"hash\"])\n\n    console.log(\"admin \" + admin)\n\n    if(!await checkURL(req[\"url\"]) || (!admin && !demo_mode && !await checkWhite(new URL(req[\"url\"]).host))){\n\n    // 非演示模式下,非白名单地址当成地址不合法处理,\n\n    return new Response(`{\"status\":500,\"key\":\": Error: Url illegal.\"}`, {\n\n      headers: {\n\n      \"content-type\": \"text/html;charset=UTF-8\",\n\n      \"Access-Control-Allow-Origin\":\"*\",\n\n      \"Access-Control-Allow-Methods\": \"POST\",\n\n      },\n\n    })}\n\n    let stat,random_key=await save_url(req[\"url\"], req[\"key\"], admin)\n\n    console.log(\"stat \" + stat)\n\n    if (typeof(stat) == \"undefined\"){\n\n      return new Response(`{\"status\":200,\"key\":\"/`+random_key+`\"}`, {\n\n      headers: {\n\n      \"content-type\": \"text/html;charset=UTF-8\",\n\n      \"Access-Control-Allow-Origin\":\"*\",\n\n      \"Access-Control-Allow-Methods\": \"POST\",\n\n      },\n\n    })\n\n    }else{\n\n      return new Response(`{\"status\":200,\"key\":\": Error:Reach the KV write limitation.\"}`, {\n\n      headers: {\n\n      \"content-type\": \"text/html;charset=UTF-8\",\n\n      \"Access-Control-Allow-Origin\":\"*\",\n\n      \"Access-Control-Allow-Methods\": \"POST\",\n\n      },\n\n    })}\n\n  }else if(request.method === \"OPTIONS\"){  \n\n      return new Response(``, {\n\n      headers: {\n\n      \"content-type\": \"text/html;charset=UTF-8\",\n\n      \"Access-Control-Allow-Origin\":\"*\",\n\n      \"Access-Control-Allow-Methods\": \"POST\",\n\n      },\n\n    })\n\n  \n\n  }\n\n  \n\n  const requestURL = new URL(request.url)\n\n  const path = requestURL.pathname.split(\"/\")[1]\n\n  console.log(path)\n\n  if(!path){\n\n  \n\n    const html= await fetch(`https://cdn.jsdelivr.net/gh/${github_repo}${github_version}/index.html`)\n\n    const text = (await html.text())\n\n        .replaceAll(\"###GITHUB_REPO###\", github_repo)\n\n        .replaceAll(\"###GITHUB_VERSION###\", github_version)\n\n        .replaceAll(\"###DEMO_NOTICE###\", demo_notice)\n\n    return new Response(text, {\n\n    headers: {\n\n      \"content-type\": \"text/html;charset=UTF-8\",\n\n    },\n\n  })\n\n  }\n\n  const url = await load_url(path)\n\n  if (!url) {\n\n    // 找不到或者超时直接404,\n\n    console.log('not found')\n\n    return new Response(html404, {\n\n      headers: {\n\n        \"content-type\": \"text/html;charset=UTF-8\",\n\n      },\n\n      status: 404\n\n    })\n\n  }\n\n  return Response.redirect(url, 302)\n\n}\n\n  \n\naddEventListener(\"fetch\", async event => {\n\n  event.respondWith(handleRequest(event.request))\n\n})\n```\n\n5. 最后一步,添加自定义域名,域名需先托管到cloudflare解析,此时你就可以通过这个域名访问并生成短链接了。\n![](https://s3.bmp.ovh/imgs/2022/09/21/c37a4a51bfd43137.png)\n![](https://s3.bmp.ovh/imgs/2022/09/21/ec642a71175ef4b2.png)\n\n\n## 参考\n> https://github.com/AoEiuV020/Url-Shorten-Worker\n\n\n\n\n\n\n",
  "attributes": [
    {
      "value": "2022-09-21---Personal-short-chain-services.md",
      "trait_type": "xlog_slug"
    }
  ]
}