侧边栏壁纸
博主头像
Terry

『LESSON 5』

  • 累计撰写 90 篇文章
  • 累计创建 21 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

TinyUrl(短链接)设计

Terry
2020-11-07 / 0 评论 / 0 点赞 / 341 阅读 / 1,762 字 / 正在检测是否收录...

简述

在生活中,我们会通过url访问各种网站,但是一般url太长的话,我们发布到微博,发送短信等都是有长度限制的,这时候可能一条url就超出范围了。所以我们会使用tinyUrl来协助我们解决这类问题。

短链接好处

  • 发短信,发布评论等一般都会有长度限制,短链接更好地帮助我们减少url占用的长度,从而能够填写更多的信息。
  • 短链接比较简短,我们有时候可以直接拼出短链接去访问网站(虽然一般也不这么弄)。

总的来说,短链接就是用来解决占用长度问题,一条100字节的url使用tinyUrl可能只要30字节。

短链接设计

短链接一般都是由数字和字母组成,组成比较短的组合。我这边使用了自增ID的方法,对ID进行了Base58编码。Base58编码就是相比Base64编码不使用数字"0",字母大写"O",字母大写"I",和字母小写"l",以及"+“和”/"符号,因为这些字母数字容易让客人看错。

自增ID

自增ID是一种无碰撞的方法,依靠数据库生成唯一ID,然后通过Base58把十进制的ID编码,转成了一个58进制的字符串。
然后解码的时候,把字符串转成十进制即可。

数据库设计

create table tiny_url
(
    id          bigint        not null comment '主键编号'
        primary key,
    url         varchar(1000) null comment 'url',
    create_date datetime      null comment '创建时间',
    expire_date datetime      null comment '过期时间',
    last_update datetime      null comment '最后访问时间',
    create_ip   varchar(50)   null comment '创建IP',
    state       int(2)        null comment '状态(-1-删除 0-禁用 1-正常)'
);

库表设计中,我们可以根据自身的需求来设计,不过最基础的数据也必须存在的。如上面创建表的SQL语句,这是基础的信息。

过期时间设置

我们可以在短链接表中添加过期时间,然后在程序中设计定时任务,定时去扫描表中过期的数据。如果过期了,将数据进行逻辑删除,避免再次使用。

缓存设计

我们可以使用Caffeine本地缓存,设置好缓存过期时间,当超过过期时间后缓存自动移除(这要看具体业务)。当获取短链接的时候,会从缓存中获取,如果获取不到Caffeine从数据库中获取。这是基础处理方式,如果要应对缓存击穿、穿透和雪崩,要自行处理(短链接一般不会有这些问题)。

短链接跳转流程

  1. 客人访问短链接。
  2. 从短链接中获取到Base58编码后的ID,然后把ID解码,获取到对应的数据库主键ID。
  3. 根据此ID从缓存中获取长链接,如果获取到则返回缓存数据;如果获取不到则从数据库中查询,返回长链接。
  4. 如果长链接不存在,则进行错误统计,返回404;否则返回HTTP状态码302,跳转到长链接。

以上是短链接跳转的基本流程,你也可以添加ip限制等处理,不让客人频繁访问。我们这里使用了HTTP状态码302了,网上也说用301更好,但是业务上不太需要,所以没使用。
为什么要使用302跳转,而不是301跳转呢?

301是永久重定向,302是临时重定向。短地址一经生成就不会变化,所以用301是符合http语义的。但是如果用了301, Google,百度等搜索引擎,搜索的时候会直接展示真实地址,那我们就无法统计到短地址被点击的次数了,也无法收集用户的Cookie, User Agent 等信息,这些信息可以用来做很多有意思的大数据分析,也是短网址服务商的主要盈利来源。原文链接

Base58算法

public class Base58Utils {

    private static final char[] DIGITS_58_CODES = {'T', 'o', '9', 'u', 'M', 'W', 's', 'C', '6', 'p', '8', 'g', 'e', 'Z', 'f', 'k', 'N', 'J', 'i', 'v', 'r', '4', 'h', 'b', 'j', 'D', 'z', 'Q', 'G', 'Y', 'A', 't', 'a', 'd', 'R', 'x', 'c', '5', 'q', 'K', 'n', 'H', 'E', 'U', 'y', 'V', 'm', '2', 'w', 'B', 'X', '7', 'S', 'F', 'L', '3', 'P', '1'};

    private static int DIGITS_LEN = 58;

    /**
     * base 58编码。
     *
     * @param num
     * @return
     */
    public static String encodeBase58(long num) {
        char[] buf = new char[32];
        int charPos = 32;
        while ((num / DIGITS_LEN) > 0) {
            buf[--charPos] = DIGITS_58_CODES[(int) (num % DIGITS_LEN)];
            num /= DIGITS_LEN;
        }
        buf[--charPos] = DIGITS_58_CODES[(int) (num % DIGITS_LEN)];
        return new String(buf, charPos, (32 - charPos));
    }

    /**
     * base 58解码。
     *
     * @param code
     * @return
     */
    public static long decodeBase58(String code) {
        char[] charBuf = code.toCharArray();
        long result = 0, base = 1;

        for (int i = charBuf.length - 1; i >= 0; i--) {
            int index = 0;
            for (int j = 0, length = DIGITS_58_CODES.length; j < length; j++) {
                // 找到对应字符的下标,对应的下标才是具体的数值
                if (DIGITS_58_CODES[j] == charBuf[i]) {
                    index = j;
                }
            }
            result += index * base;
            base *= DIGITS_LEN;
        }
        return result;
    }

    /**
     * 乱序加密数字。
     *
     * @param num
     * @return
     */
    public static long encodeNum(long num) {
        if (num < 0) {
            num = Math.abs(num);
        }
        String data = String.valueOf(num);
        int mask =(int) (num % 9) + 1;
        int pos = mask;
        if (mask > data.length()) {
            pos = mask % data.length();
        } else if (mask == data.length()) {
            pos = 1;
        }
        num = Long.parseLong(mask + data.substring(pos) + data.substring(0, pos));
        return num;
    }

    /**
     * 乱序解密数字。
     *
     * @param num
     * @return
     */
    public static long decodeNum(long num) {
        String data = String.valueOf(num);
        int mask = Integer.parseInt(data.substring(0, 1));
        int len = data.length() - 1;
        int pos = mask;
        if (mask > len) {
            pos = mask % len;
        } else if (mask == len) {
            pos = 1;
        }
        pos = len - pos;
        return Long.parseLong(data.substring(pos + 1) + data.substring(1, pos + 1));
    }
    
}

总结

我们在生活中经常使用短链接。发短信,发布评论等一般都会有长度限制,短链接更好地帮助我们减少url占用的长度,从而能够填写更多的信息。
短链接的实现也不复杂,使用了数据库自增长ID,然后通过Base58编码,使得十进制的ID转成了58进制,大大缩短了ID长度。然后通过数据库中实际存放的长链接重定向,体现短链接作用。

0

评论区