前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >短链接生成太无聊?试试看长链接生成,URL地址变成乐谱音符?

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

原创
作者头像
Mintimate
发布2023-09-23 01:37:25
5312
发布2023-09-23 01:37:25
举报
文章被收录于专栏:Mintimate's BlogMintimate's Blog
头图?这是能少的?
头图?这是能少的?

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

短链接

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

原理就是:

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

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

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

长链接

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

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

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

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

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

网站内容
网站内容

举个例子:你输入网址:/developer,那么可以得到:

得到的字符串
得到的字符串

访问得到的字符串,发现实际上重定向到了:/developer

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

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

原理解析

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

核心代码就是这块:

核心代码
核心代码

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

网站的原理
网站的原理

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

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

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

UTF-8数组

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

UTF-8
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)
    }

最后,看看效果,输入:

代码语言:text
复制
/developer

输出:

代码语言:text
复制
???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
编码成功
编码成功

接下来看看如何网站上实现。

Nuxt3上实现

我们需要达成一个302的重定向跳转。在SpringBoot中,你可以使用RedirectView进行跳转:

代码语言:java
复制
@GetMapping("/path")
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 === ?developer/article/2332325/undefined || b5str.length ===0){
        return sendRedirect(event, "/404", 302);
    }

    let utf8arr = [];
    // 每次解析5个字符
    // 记住添加前导0的填充
    for (let i = 0; i < 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腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 短链接
  • 长链接
  • 原理解析
    • UTF-8数组
      • 数组映射
        • 解码
        • 复刻为乐谱
        • Nuxt3上实现
        • END
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
        http://www.vxiaotou.com