短网址

      顾名思义,短网址就是一个很短的链接而已。在生活中我们经常能收到诸如以下短信。
请输入图片描述

      若我们点开此链接会发现实际的URL十分长。

https://fast-wallet.finzjr.com/file/marketing/20210624/f1739910-5531-4380-b33b-d8dc94d294b1.html?fy=1&from=701&e_channel=duanxin_new_dianshang_0801

      似乎这只是做了一个转换呀!有什么高级的呢?长链不行吗?这是怎么做到的?这是我初次遇见短链时抛出的种种问题,相信大家也会由此困惑。接下来将一一解答。

短链的优势

  • 短信发送是有字数限制的,若发送长链可能连广告内容都没处写了;
  • 短链在内容上对用户友好,不会像长链一样一堆字符;
  • 链接太长在有些平台上无法自动识别为超链接,只能用短链

短链的基本原理

      通过简单的浏览器抓包我们可以很快速的了解短链跳转的基本原理:
请输入图片描述

      可以看到请求短链后,返回了状态码 301(重定向)与 location 值为长链的响应,然后浏览器会再请求这个长链以得到最终的响应,整个交互流程图如下:
请输入图片描述

不难猜出在短网址系统中肯定做了短链与长链的对应,查询出对应长链后直接重定向。

短链的生成

      短链是由固定短链域名 + 长链映射成的一串字母组成的,生成的那一串字母就是整个短网址系统的核心所在,如何将长链转换为一小段字符是首要考虑的任务。

Hash?

      上述提到短网址系统中做了短链与长链的映射,提起映射大家首先想到的就是Hash了,将长链使用相应的Hash算法生成对应的HashCode,可以以此为短链。

很遗憾,基于Hash的生成会导致哈希冲突,对大流量的网站不合适。而我们无法难以设计一个没有哈希冲突的算法

随机再去重?

      除了使用Hash算法这一高大上的方式,我们可以随机生成一个6为的短链,首先去数据库中查询是否存在,若存在则重新生成,否则直接使用。
请输入图片描述

      代码实现如下所示:

private String longToShort(String longUrl){
    while(true){
        String shortUrl = UUID.randomUUID().toString().replaceAll("-","");
        UrlEntity urlEntity = urlMapper.selectByShortUrl(shortUrl);
        if(urlEntity != null){
            // 有重复
            continue;
        }
        // 没重复,直接插入
        urlMapper.insertShortUrl(shortUrl,longUrl);
    }
}

      数据表如下所示:

+----+-----------+----------------------------+
| id | short_url |          long_url          |
+----+-----------+----------------------------+
|  1 |  feafea   + http://www.chengpengper.cn |
|  2 |  sfevef   + http://www.chengpengper.cn |
|  3 |  zdfesa   + http://www.chengpengper.cn |
|  4 |  csedaq   + http://www.chengpengper.cn |
|  5 |  cesade   + http://www.chengpengper.cn |
|  6 |  jkonlo   + http://www.codelife.cn     |
|  7 |  podexe   + http://www.codelife.cn     |
+----+-----------+----------------------------+

随机再去重的方案实现简单,但是其生成短网址的速度会随着数据量的增加而变得越来越慢

62进制

      为什么是62进制?那是因为0-9、a-z、A-Z共62个字符。
      我们将一个6位的shortUrl看做一个62进制数,如'feafea'对应的十进制为13951303002。若我们能生成一个唯一的十进制数,自然可以将其转换为62进制数。在单体系统中我们可以借助MySql的自增主键完成唯一ID的生成,再将其转换为62进制的6位短链,而通过短链查询长链只需要将62进制转为十进制(即ID),根据ID查询即可。
请输入图片描述

如上图生成短链中我们没有去判断数据库中是否已经有此长链了,而是无脑的insert,可以在插入前根据长链查找是否已经存在了,存在则直接返回,否则执行后续逻辑

      进制转换核心代码如下所示:

public class UrlUtils {

    static final String str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    static final int MAX_LEN = 6;


    /**
     * 进制转换 Base62
     */

    /**
     * id 转 shortUrl
     * @param shortUrl 短网址
     * @return 短网址对应的 id
     */
    public static long shortUrlToId(String shortUrl){
        long id = 0;
        char[] chars = shortUrl.toCharArray();
        for(int i=0;i < chars.length; i++){
            id = id * 62 + toBase62(chars[i]);
        }
        return id;
    }

    private static int toBase62(char ch){
        return str.indexOf(ch);
    }

    /**
     * id 转 短网址
     * @param id id
     * @return id 对应的短网址
     */
    public static String idToShortUrl(Long id){
        StringBuilder shortUrl = new StringBuilder();
        while ( id > 0){
            shortUrl.append(str.charAt((int)(id % 62)));
            id /= 62;
        }
        while(shortUrl.length() < MAX_LEN){
            shortUrl.append("0");
        }
        return shortUrl.reverse().toString();
    }

}

重定向

      重定向跳转的核心方法如下所示:

@GetMapping("/{shortUrl}")
public String shortTolongUrl(@PathVariable String shortUrl, HttpServletResponse response){
    String longUrl = urlService.shortTolong(shortUrl);
    response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);//301
    response.setHeader("Location", longUrl);
    response.setHeader("Connection", "close");
    return "redirect:"+longUrl;
}

数据库变化

      和随机生成再去重中的数据表不同,我们不需要再保存具体的短网址字符串了。

+----+----------------------------+
| id | long_url                   |
+----+----------------------------+
|  1 | http://www.chengpengper.cn |
|  2 | http://www.chengpengper.cn |
|  3 | http://www.chengpengper.cn |
|  4 | http://www.chengpengper.cn |
|  5 | http://www.chengpengper.cn |
|  6 | http://www.codelife.cn     |
|  7 | http://www.codelife.cn     |
+----+----------------------------+

为什么是6位的长度呢?这就需要看6位的62进制数能表示多少URL了(估算):

  • 5位 = 62^5 = 9亿
  • 6位 = 62^6 = 570亿
  • 7位 = 62^7 = 35000亿

所以,6位的62进制足够我们使用了。

自定义短链

      有些时候用户不希望系统自动生成短链,而是希望自定义短链。

新建自定义短链表

      不建议在原有的表中添加一列表示自定义短链,因为自定义短链的使用相对较少,否则会导致表中大量空缺。所以直接新建一个表吧~

DROP TABLE IF EXISTS `test_custom`;
CREATE TABLE `test_custom`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `custom_url` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '自定义短链',
  `long_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '长链',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `custom_long_unq`(`custom_url`, `long_url`) USING BTREE COMMENT '建立自定义url和长链的唯一索引,防止重复'
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

交互图

      在短链的跳转部分与之前发生了一些改变,需要先查询custom表,判断是否是自定义短链。
请输入图片描述

301 or 302?

      从计算机网络了解到重定向有301重定向和302重定向,并且短网址系统中也有使用这两种重定向方式的,到底应该选择哪种重定向方式呢?

  • 301,代表 永久重定向,也就是说第一次请求拿到长链接后,下次浏览器再去请求短链的话,不会向短网址服务器请求了,而是直接从浏览器的缓存里拿,这样在 server 层面就无法获取到短网址的点击数了,如果这个链接刚好是某个活动的链接,也就无法分析此活动的效果。所以我们一般不采用 301。
  • 302,代表 临时重定向,也就是说每次去请求短链都会去请求短网址服务器(除非响应中用 Cache-Control 或 Expired 暗示浏览器缓存),这样就便于 server 统计点击数,所以虽然用 302 会给 server 增加一点压力,但在数据异常重要的今天,这点代码是值得的,所以推荐使用 302!
Last modification:August 22nd, 2021 at 09:41 pm
如果觉得我的文章对你有用,请随意赞赏