短链原理与实现方案

短链原理与实现方案

一:背景

发送邮件通知,嵌入链接跳转到系统后台页面,如果链接过长,消息体显示的时候会影响布局与美观,所以需要将原始的http长链接转为短链接显示,当点击短链接跳转时,采用301或302状态码的方式将短链重定向到长链。

二:技术方案

  • 基本原理:

1.将长链通过技术手段生成一个短链接去页面展示。

2.点击访问短链接,通过短链接服务去数据库找到对应长链接。

3.重定向跳转。

短链原理与实现方案_第1张图片

  • 短链生成:

首先,我们需要建立一个表去映射长链接与短链接。

mysql表:

CREATE TABLE `t_url_mapping` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `lurl` varchar(255) DEFAULT NULL COMMENT '长地址',
  `surl` varchar(12) DEFAULT NULL COMMENT '短地址',
  `md5` char(32) DEFAULT NULL COMMENT '长链的加盐md5',
  `createTime` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `t_url_mapping_surl_IDX` (`surl`) USING BTREE,
  KEY `t_url_mapping_md5_IDX` (`md5`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8hgn5AGY-1661930146074)(C:\Users\cherish\AppData\Roaming\Typora\typora-user-images\image-20220831144615566.png)]

短链生成具体步骤:

1.根据长链(longUrl)进行计算,salt_md5=MD5(longUrl + salt),这里可以使用guava 的 MurmurHash 工具类。

guava依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

2.根据salt_md5去mysql查看有没有对应的longUrl_1。

3.如果有对应的longUrl_1,需要判断longUrl_1是否与longUrl相同,相同即返回,不相同继续往下执行。

4.从发号器获取全局唯一id。(可以使用redis,雪花算法,数据库自增序列)

5.生成短链:short_url = (id的62进制值) + “-” + salt_md5的前五位,62进制使用字符0-9,a-z,A-Z。

6.写入数据库。

具体实现代码:

@Component
public class ShortLinkGenerator {

    private static final String CHARS_62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    private static final int SCALE = 62;

    private static final char SEPARATOR = '-';

    private static final String SALT = "HelloWorld";

    @Resource
    private UrlMappingService urlMappingService;

    @Resource
    private SequenceGenerator sequenceGenerator;

    /**
     * 将数字转为62进制
     *
     * @param num Long 型数字
     * @return 62进制字符串
     */
    public static StringBuilder encode(long num) {
        StringBuilder sb = new StringBuilder();
        int remainder = 0;
        while (num > SCALE - 1) {
            /**
             * 对 scale 进行求余,然后将余数追加至 sb 中,由于是从末位开始追加的,因此最后需要反转(reverse)字符串
             */
            remainder = Long.valueOf(num % SCALE).intValue();
            sb.append(CHARS_62.charAt(remainder));

            num = num / SCALE;
        }
        sb.append(CHARS_62.charAt(Long.valueOf(num).intValue()));
        return sb.reverse();
    }

    /**
     * 生成短链的方法
     * @param originalUri 原uri
     * @return
     */
    public String generate(String originalUri) {
        //  hash
        HashCode hashCode = Hashing.murmur3_128().hashString(originalUri + SALT, StandardCharsets.UTF_8);
        //  根据hash去数据库中查询是否有对应的记录
        String md5 = hashCode.toString();
        UrlMapping urlMapping = urlMappingService.queryByMd5(md5);
        if (null != urlMapping) {
            if (urlMapping.getLurl().equals(originalUri)) {
                //  存在直接返回
                return urlMapping.getSurl();
            }
        }
        //  获取全局唯一id,这里可以采用redis自增,雪花算法等也可实现。
        Long uniqueId = sequenceGenerator.nextValue("shortLink");
        StringBuilder encode = ShortLinkGenerator.encode(uniqueId);
        //  得到短链
        StringBuilder shortUri = encode.append(ShortLinkGenerator.SEPARATOR).append(md5, 0, 4);
        String result = shortUri.toString();
        UrlMapping entity = UrlMapping.builder()
                .surl(result)
                .lurl(originalUri)
                .md5(md5)
                .createTime(LocalDateTime.now())
                .build();
        // 入库
        urlMappingService.insert(entity);
        return result;
    }


}
  • 短链跳转服务:
@Slf4j
@RequestMapping("/")
@RestController
public class ShortUrlController {
    @Resource
    private UrlMappingMapper urlMappingMapper;

    @GetMapping("/{shortUrl}")
    public void redirectView(HttpServletResponse resp,  @PathVariable("shortUrl") String shortUrl) {
        // 查询对应长链
        UrlMapping urlMapping = urlMappingMapper.queryBySurl(shortUrl);
        String lurl = urlMapping.getLurl();
        // 设置302状态码并重定向
        resp.setStatus(302);
        resp.setHeader("Location",lurl);
        try {
            resp.sendRedirect(lurl);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }
}
  • 说明:

1.为什么加盐?

答:为了避免用id反解出md5字符串

2.为什么在生成短链时包含id值?

答:md5值存在hash碰撞,但是id值是全局唯一的,可以保证短链的全局唯一性。

3.既然id全局唯一,为什么不直接用id做短链?

答:只用id做短链,用户可以自己修改短链id,进而非法访问其他短链接。

4.为什么对id进行62进制编码?

答:为了使短链的长度更短。

:为了避免用id反解出md5字符串

2.为什么在生成短链时包含id值?

答:md5值存在hash碰撞,但是id值是全局唯一的,可以保证短链的全局唯一性。

3.既然id全局唯一,为什么不直接用id做短链?

答:只用id做短链,用户可以自己修改短链id,进而非法访问其他短链接。

4.为什么对id进行62进制编码?

答:为了使短链的长度更短。

你可能感兴趣的:(mysql,数据库,java)