前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >短链接的实现

短链接的实现

作者头像
时光潜流
发布2023-10-22 16:22:43
3400
发布2023-10-22 16:22:43
举报
文章被收录于专栏:博客专栏博客专栏

短链接,可以将一个较长的、携带参数的url简化成一个可以接受的长度。

生活中,经常会在手机短信的广告中出现,因为短信服务本身对短信的长度有限制,如果使用一个非常长的链接,几百字符很快就能用完,关键信息的字符数被挤压,影响了服务方的广告价值同时也影响了消费者的观感,通过短链可以解决这个问题。

短链也经常用在资源的分享链接上,比如常用的百度网盘资源分享,访问他人分享的资源链接,可以看到先经过了302重定向到一个其它的地址,这样分享资源的人就只要发/s/后面的一段字符串,接受分享的人也只要记住简单的百度网盘资源前缀pan.baidu.com/s/即可,简化了结构与流程,便于传递信息。

当然百度的短链并非完全是这样的功能,如果访问了自己的资源,我们会发现,状态码是200,即表示直接将页面渲染了,这样巧妙的将不同逻辑的相似功能整合到了一个短链的访问上。

今天通过百度下载了动漫资源《别当欧尼酱了!》,并且制作了一个简单的前端页面上传到服务器,从而可以在线查看该动漫。这样做的原因嘛,当然是因为太喜欢这部动漫了,所以打算珍藏起来反复回味!

不过部署后,分享资源时的链接是这样的????https://www.dreamcenter.top/extra/onimai/index.html ,好嘛,链接结构还是比较复杂的,长度也不是很友好,于是就想到了之前使用的别人的短链服务。但是一直使用别人的服务,倘若服务被关闭了,势必会造成一些难以挽救的局面,所以就想着自己设计实现一个短链服务。

基本原理

短链的原理其实是非常简单的,关键就是 资源映射表 + 重定向 。

资源映射表可以通过各种数据来记录,记录短链与实际资源地址的映射关系。

重定向有两种方案,一种是使用301重定向,其特点是一次访问后,会在本地缓存,之后的访问会直接到达目标网址,不用再走一遍解析的流程;另一个种是使用302重定向,这种重定向是暂时的,下次访问短链还是会实际访问解析,这样的好处是可以对短链业务进行一些扩展与数据统计,不过会稍微增加一些服务压力。可以根据实际的需求来选择哪条路线。

设计实现

我采用的路线是 mysql 记录映射关系,301重定向。

短链接的一个比较重要的设计内容是 代表指定资源的特征字符串,常见的长度是6~8位,构成为英文数字组合,如 2H16sD 。当然也有一些比较特别的业务需求,该字符串会设计的更加复杂。该字符串我一开始想到的就是hash,倘若碰撞了,那么下位再次判定,但是参考了别人的设计方案,发现这种方案在短链很多的情况下,碰撞几率还是相当大的,数据越多,综合的生成效率越低。那么怎样才能稳定的效率生成呢?

常用的方式是自增值62进制。为什么用62进制?仔细数0-9、a-z、A-Z,会发现正好有62位,倘若使用64进制的话,就势必有两个符号位加入生成,而很多符号在链接中都有特殊含义,不易选取。而62进制就不用担心那类问题了。将十进制转任意进制应该算是算法基本功了吧,简单贴一个实现。使用long入参是因为redis自增返回的是long类型,而且long也能存储更多的短链。

代码语言:javascript
复制
public class SpecificEncoding {

    private static final String STR_SER = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    public static String base62(long val) {
        StringBuilder sb = new StringBuilder(); // 64
        while (val>0) {
            int tmp = (int)(val % 62); // [0,62) => i2 => 2 | => i0 => 1
            sb.append(STR_SER.charAt(tmp)); // 21
            val /= 62; // => val = 1 => 1 | 0
        }
        return sb.reverse().toString();
    }

}

业务层面,自增变量一开始考虑到的是数据库映射表记录数量+1,但是后来仔细一想,如果删了中间的一条记录,那么下一个生成必然重复了。所以这个自增变量必须是绝对的不断向前滚动+1 的,我采用了一个redis来存储与操作该自增变量,实现如下。仔细阅读会发现在进行32进制编码前,传递的值是 Integer.MAX_VALUE + increment,为什么要加int的最大值呢?答案是显而易见的,为了让长度达到最少6位,当然达到6位的临界值没有细算,只是发现Integer.MAX_VALUE通过生成后是2开头的6位字符串,也就是说并没有浪费多少,还有很多很多的数据量可以存储。

代码语言:javascript
复制
  @Autowired
    private ShortLinkDao shortLinkDao;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public RetResult<Integer> newShortLink(String raw) {
        ValueOperations ops = redisTemplate.opsForValue();
        Long increment = ops.increment(RedisConst.SHORT_LINK_ID);
        if (increment == null) return RetResult.fail("服务器出现异常...");
        String link = SpecificEncoding.base62(Integer.MAX_VALUE + increment);
        ShortLink shortLink = new ShortLink(link, raw);
        return RetResult.success(shortLinkDao.insert(shortLink));
    }

最终的控制层面就更加简单了,除了基本的增删查服务调用外,额外的,就是通过路径变量比对映射表得到实际地址后进行重定向,一般无需更改服务,与其更改,不如再来一个新的来的方便(注意功能的访问权限控制)。如果经常要解析短链的,建议在解析服务与数据库之间加入一层redis缓存来处理热门解析。

代码语言:javascript
复制
package top.dreamcenter.dreamcenter.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import top.dreamcenter.dreamcenter.entity.ServerProperties;
import top.dreamcenter.dreamcenter.entity.ShortLink;
import top.dreamcenter.dreamcenter.ret.RetResult;
import top.dreamcenter.dreamcenter.service.ShortLinkService;

import java.util.List;

@Controller
public class ShortLinkController {

    @Autowired
    private ShortLinkService shortLinkService;

    @Autowired
    private ServerProperties serverProperties;

    @ResponseBody
    @PostMapping("/sl/new")
    public RetResult<Integer> newShortLink(String raw) {
        return shortLinkService.newShortLink(raw);
    }

    @ResponseBody
    @GetMapping("/sl/del")
    public RetResult<Integer> delShortLink(String link) {
        return shortLinkService.delShortLink(link);
    }

    @GetMapping("/s1/{link}")
    public String getRaw(@PathVariable String link) {
        String raw = shortLinkService.getRaw(link).getData();
        return "redirect:" + raw;
    }

    @ResponseBody
    @GetMapping("/sl/all")
    public RetResult<List<ShortLink>> getAllShortLinks() {
        return shortLinkService.getAllShortLinks();
    }
}

最终效果

通过前端配合设计,并且把之前的资源生成短链后,得到的效果如下:

可以发现之前的《别当欧尼酱了》短链形式是 https://www.dreamcenter.top/s1/2LKcb2 ,变得非常简洁。对于大家可以将中间路由设置成和百度网盘类似的/s/来区分业务请求,如果该短链接独占该ip的80、443端口,可以直接将短链字符串至于/根路径下,会变得更加简洁。

为了方便短链复制,可以使用clipboard.js来处理内容一键复制功能。

如果想要进一步增加需求,让数据更加安全,上面的这种连贯的方案肯定是不够安全的,可以增加一些随机位、位置交换、矩阵计算等方案让数据变得相对安全些。

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-05-13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

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

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本原理
  • 设计实现
  • 最终效果
相关产品与服务
短信
腾讯云短信(Short Message Service,SMS)可为广大企业级用户提供稳定可靠,安全合规的短信触达服务。用户可快速接入,调用 API / SDK 或者通过控制台即可发送,支持发送验证码、通知类短信和营销短信。国内验证短信秒级触达,99%到达率;国际/港澳台短信覆盖全球200+国家/地区,全球多服务站点,稳定可靠。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com