短链接生成太无聊?试试看长链接生成,URL地址变成乐谱音符🎵

头图?这是能少的?

博客:https://www.mintimate.cn Mintimate’s Blog,只为与你分享

短链接

短链接,相信大家再熟悉不过了。比如之前写过的文章:

  • 搭建短链接平台详细分析及具体代码实现: https://cloud.tencent.com/developer/article/1860739

原理就是:

  • 生成唯一ID,用于存储当作查询的唯一键;
  • 存储的唯一键,映射到完整的URL地址上;
  • 使用302/301进行重定向跳转,建议需要统计访问量使用302,不需要统计访问量或者完成映射后不再更改,使用301。

用短链接替换较长的原始 URL,使得用户在访问网页或资源时可以使用更短、更便于记忆和分享的链接,也方便隐藏Get请求。

但是,这样的短链接,还是缺少一些乐趣。从算法和乐趣触发,长链接,了解一下?

长链接

其实并没有公认的长链接定义,我之所以称本次内容为长链接生成,是因为本次介绍的算法效果,和短链接最后达成的效果相反。

这一切源自于我看到这个网站:

  • https://ooooooooooooooooooooooo.ooo

这个网站看起来全都是o,但是你细看再细看,就会发现,实际上包含4四种不同的字符:

  • o是英语小写字母o
  • ο是希腊字母omicron
  • о是西里尔字母о
  • 是小号形式的字母o

当然,更有趣的是,这个网站所做的内容:

网站内容

举个例子:你输入网址:https://cloud.tencent.com/developer,那么可以得到:

得到的字符串

访问得到的字符串,发现实际上重定向到了:https://cloud.tencent.com/developer。

也就是把一个URL链接,变长和风格化了。

那么,是怎么做到的呢? 又是如何复现呢?

原理解析

嘿嘿,本来想F12看看是不是前端实现的,结果发现作者直接开源了这个好玩的网站:

  • https://github.com/lucaceriani/ooo

核心代码就是这块:

核心代码

这个时候,我们就知道原理了:

网站的原理

简单地说,访问访问这个网站,如果存在二级目录,那么:

  • 截取二级目录内容,尝试映射为UTF-8字符数组;
  • 成功映射的情况,还原UTF-8字符串数组为原始URL并跳转;
  • 映射失败或者不存在二级目录,直接进入主页。

为什么使用UTF-8数组进行字段的映射呢?

UTF-8数组

首先,我们要知道UTF-8是Unicode的一种字节序列表示形式(编码方案),UTF-8将一个Unicode字符根据其码点转化为1-4个字节的序列来存储和传输

UTF-8

基础的Unicode定义了从0到1114111之间的码位空间,用于表示世界上主流文字系统中的字符。

例如:

  • 字母A的Unicode码点是0x0041,数字0的码点是0x0030
  • 汉字也都有自己的Unicode码点,如"人"字的码点是0x4EBA。

回到UTF-8,因为UTF-8为1-4个字节的序列,所以可以用UTF-8数组来表示,比如你好世界:

  • "你"字符的Unicode码点是0x4F60,0x4F60在UTF-8编码为3个字节数字序列: [228, 189, 160]
  • "好"字符的Unicode码点是0x597D,0x597D在UTF-8编码为3个字节数字序列: [229, 165, 189]

所以,"你好世界"每个字符的UTF-8编码数组是:

代码语言:text
复制
[228, 189, 160, 229, 165, 189, 224, 168, 104, 227, 174, 164]

根据用例,转换过程就是:

  1. 查找每个字符在Unicode标准中的码点编号
  2. 根据UTF-8编码规则,将码点转化为1-4个字节的数字序列
  3. 把各个字节序列整合成一个数字数组

这样就完成了从字符串到UTF-8编码数组的转换。并且,新的数组一定是1~4个数字序列,每个字符序列,由高到低排序

数组映射

综合上述的UTF-8数组,我们可以把任意的字符全部转为UTF-8的数组,并且数据内部全部是数组。

这个时候,是怎么映射为o呢?

关键代码:

代码语言:javascript
复制
enc = ["o", "ο", "о", "ᴏ"]

encodeUrl(url) {
// get utf8 array
let unversioned = this.toUTF8Array(url)
// convert to string with base 4
// padstart very important! otherwise missing leading 0s
.map(n => n.toString(4).padStart(4, "0"))
// convert to array of characters
.join("").split("")
// map to the o's
.map(x => this.enc[parseInt(x)])
// join into single string
.join("")

return this.addVersion(unversioned)

}

核心逻辑:

  • 数组中的元素转4进制字符串,前位补0;
  • 连接成的长字符串,再切割成单字符数组;
  • 每个字符映射成字母表字符(四个不同的o);
  • 字符数组连接成新的字符串。

这样,原本的你好世界,就被映射为:ᴏоοoоᴏᴏοооooᴏоοοооοοоᴏᴏο

解码

恢复就很简单了,上一节的操作反着进行就可以了:

代码语言:javascript
复制
decodeUrl(ooo) {

ooo = this.removeAndCheckVersion(ooo)
if (ooo === null) return

// get the base 4 string representation of the url
let b4str = ooo.split("").map(x => this.dec[x]).join("")

let utf8arr = []

// parse 4 characters at a time (255 in b10 = 3333 in b4)
// remember adding leading 0s padding
for (let i = 0; i < b4str.length; i += 4)
    utf8arr.push(parseInt(b4str.substring(i, i + 4), 4))

return this.Utf8ArrayToStr(utf8arr)

}

可以看到,还是很简单的。就是思路,非常新颖。

复刻为乐谱

掌握了原理,我们就可以复刻为音符的版本了,既然原版使用四个不同的o,那么我们可以使用特殊符号:"♫", "♪", "♬", "¶","♩"

首先是定义一个env,用于映射:

代码语言:javascript
复制
const enc = ["♫", "♪", "♬", "¶","♩"]

既然使用了五个音符,那么就应该用5进制了:

代码语言:javascript
复制
  // 获取utf8数组
let UTF8Array = toUTF8Array(originUrl)
// 转换为base 4字符串
// padstart非常重要!否则会丢失前导0
.map(n => n.toString(5).padStart(5, "0"))
// 转换为字符数组
.join("").split("")
// 映射到o的不同形式
.map(x => enc[parseInt(x)])
// 连接成单个字符串
.join("");

解码也需要更改一下:

代码语言:javascript
复制
   decodeUrl(ooo) {

    ooo = this.removeAndCheckVersion(ooo)
    if (ooo === null) return

    // 每次解析5个字符
    let b5str = ooo.split("").map(x => this.dec[x]).join("")

    let utf8arr = []

    for (let i = 0; i < b5str.length; i += 5)
        utf8arr.push(parseInt(b5str.substring(i, i + 5), 5))

    return this.Utf8ArrayToStr(utf8arr)
}</code></pre></div></div><p>最后,看看效果,输入:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>text</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-text"><code class="language-text" style="margin-left:0">https://cloud.tencent.com/developer</code></pre></div></div><p>输出:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>text</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-text"><code class="language-text" style="margin-left:0">♫♫♩♫♩♫♫♩¶♪♫♫♩¶♪♫♫♩♬♬♫♫♩¶♫♫♫♬♪¶♫♫♪♩♬♫♫♪♩♬♫♫¶♩♩♫♫♩♪¶♫♫♩♬♪♫♫♩¶♬♫♫♩♫♫♫♫♪♩♪♫♫♩¶♪♫♫♩♫♪♫♫♩♬♫♫♫¶♩♩♫♫♩♫♪♫♫♩♬♫♫♫♩¶♪♫♫♪♩♪♫♫¶♩♩♫♫♩♬♪♫♫♩♪♩♫♫♪♩♬♫♫♩♫♫♫♫♩♫♪♫♫♩¶¶♫♫♩♫♪♫♫♩♪¶♫♫♩♬♪♫♫♩♬♬♫♫♩♫♪♫♫♩♬♩</code></pre></div></div><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1723068132927595776.png" /></div><div class="figure-desc">编码成功</div></div></div></figure><p>接下来看看如何网站上实现。</p><h2 id="8usgj" name="Nuxt3%E4%B8%8A%E5%AE%9E%E7%8E%B0">Nuxt3上实现</h2><p>我们需要达成一个302的重定向跳转。在SpringBoot中,你可以使用<code>RedirectView</code>进行跳转:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>java</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-java"><code class="language-java" style="margin-left:0">@GetMapping(&#34;/path&#34;)

public View handle() {
return new RedirectView("/new/path", true);
}

如果使用Nginx,可以直接激活Nginx的Lua脚本,使用Lua脚本对字符串进行编码转字符串解析后:

代码语言:text
复制
location /old-path {
rewrite ^ /new-path? permanent last;
}

我最近用Nuxt3比较多,就说一下Nuxt3上如何操作。

在Nuxt3上,编码部分就不再多说了,跳转解码,可以使用服务端路由进行实现:

代码语言:javascript
复制
import { Utf8ArrayToStr } from '~/untils/longUrlMake';

export default defineEventHandler((event) => {
const ooo = decodeURIComponent(event.context.params.encoderUrl);
console.log(ooo)
const dec = {
'♫': '0',
'♪': '1',
'♬': '2',
'¶': '3',
'♩': '4'
};
// 获取url的base 5字符串表示
let b5str = ooo
.split('')
.map((x) => dec[x])
.join('');
console.log("b5str: "+b5str)

if (b5str === undefined || b5str.length ===0){
    return sendRedirect(event, &#34;/404&#34;, 302);
}

let utf8arr = [];
// 每次解析5个字符
// 记住添加前导0的填充
for (let i = 0; i &lt; b5str.length; i += 5)
    utf8arr.push(parseInt(b5str.substring(i, i + 5), 5));
// 返回解码后的字符串
let originUrl = Utf8ArrayToStr(utf8arr);
return sendRedirect(event, originUrl, 302);

});

解码成功

这样,就可以完成映射,并且直接使用302进行跳转。如果存在解析失败,直接跳转到404的界面。

END

好啦,本次的演示就到这里。或许有小伙伴问,这样把URL变长,有什么用呢?

实际上,确实用处不大,最多也就是隐藏地址内容、隐藏Get请求参数;并且乐趣十足。

不过呢,使用UTF-8数组,确实是一个很精巧的方法,后续其他的算法,也可以进行考虑。

我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表