⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/Kristabo/article/details/136369393 「蜗牛变涡流」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

1.背景介绍

短链跳转是一种通过将长链接转换为短链接的方式,以便在互联网上进行链接共享和传播的技术。通常情况下,长链接可能由于包含大量参数或者较长的路径而显得复杂且不易记忆,而短链则是将原始长链接通过特定算法转换为较短的链接,使得它更容易分享、传播和展示。

短链跳转服务通常由第三方提供,用户可以将需要缩短的长链接提交到该服务,服务会返回一个短链接,当用户访问这个短链接时,会被重定向到原始的长链接地址。这种服务通常还提供了统计功能,可以跟踪短链接被点击的次数、访问来源等信息,帮助用户了解链接的传播效果。

短链跳转服务有助于美化链接、节省空间、方便分享和统计链接访问情况,因此被广泛应用于社交媒体、微博客、推广活动等各种互联网应用场景中。

比如在b站中,一个视频的网址原来是这样的:

在移动端中,点击分享按钮,复制其链接:

它会变成如下链接形式:

【Cookie、Session、Token、JWT一次性讲完-哔哩哔哩】 https://b3.tv/0SMtY

点击该链接后,你会发现浏览器的网址URL为原来的长链接形式,也就是说这其中发生了重定向 ,而这个过程就是这篇博客要提到的短链跳转了。

2.短链跳转的意义

  • 节省空间: 长链接可能会很长,不方便分享或展示,通过短链跳转可以将长链接转换为短链接,节省字符空间。
  • 美化链接: 短链看起来更简洁、美观,对于需要展示给用户或发布到社交媒体等场景更具吸引力。
  • 防止链接失效: 某些长链接可能会因为过期、失效或变动而无法访问,通过短链跳转可以在后台进行管理和更新,保证链接的可访问性。
  • 统计和跟踪: 通过短链跳转服务可以对链接的点击量、来源、地域等信息进行统计和分析,帮助用户了解链接的受众和效果。
  • 方便分享: 短链更容易复制、粘贴和分享,适用于短信、微博、邮件等分享场景,提高分享效率。
  • 隐藏原始链接: 有时候希望隐藏原始链接的信息,通过短链跳转可以起到一定的保护作用,防止泄露敏感信息。

3.SpringBoot中的代码实现

这里我们以快速入门为主,即主要实现长链到短链的映射逻辑。

1.建议短链-长链的数据库表:t_url_map:

2.映射实体

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.Instant;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UrlMap {

private Long id;

private String longUrl;

private String shortUrl;

private String username;

private Instant expireTime;

private Instant creationTime;
}

这里要添加lombok依赖:

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

3.Dao层实现

这里简单写三个关键的接口方法:根据长链找短链(若无则生成短链)、根据短链找长链(若无则跳转失败页面)、插入实体

import com.zhan.zhan215.Entity.UrlMap;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.time.Instant;
import java.util.List;

@Mapper
public interface UrlMapMapper {

UrlMap findFirstByLongUrl(@Param("longUrl") String longUrl, @Param("username") String username);

void saveUrlMap(UrlMap urlMap);

UrlMap findByShortUrl(String shortUrl);
}

对应的xml映射:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhan.zhan215.Dao.UrlMapMapper">

<select id="findFirstByLongUrl" parameterType="string" resultType="com.zhan.zhan215.Entity.UrlMap">
select *
from t_url_map
where longUrl =#{longUrl} and username = #{username}
limit 1
</select>

<!-- 在Mapper XML文件中定义保存urlMap对象的方法 -->
<insert id="saveUrlMap" parameterType="com.zhan.zhan215.Entity.UrlMap">
INSERT INTO t_url_map (shortUrl, longUrl,username)
VALUES (#{shortUrl}, #{longUrl},#{username})
</insert>

<select id="findByShortUrl" parameterType="string" resultType="com.zhan.zhan215.Entity.UrlMap">
select *
from t_url_map
where shortUrl = #{shortUrl} limit 1
</select>

4.Service层实现

import com.zhan.zhan215.Dao.UrlMapMapper;
import com.zhan.zhan215.Entity.UrlMap;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;

@Service
public class UrlMapService {

@Resource
private UrlMapMapper urlMapMapper;


// 编码
public String encode(String longUrl,String username) {

UrlMap urlMap = urlMapMapper.findFirstByLongUrl(longUrl,username);

// 看看该长链接是否存在
// 如果存在并且其对应用户名等于已有用户名,则直接给出短链接
if (urlMap != null&&username.equals(urlMap.getUsername())) {
return urlMap.getShortUrl();

} else {
// 如果不存在,则生成短链接
UrlMap urlMap1 = new UrlMap();
// 生成短链接
String shortLink = generateShortLink(longUrl,username);
// 保存短链接
urlMap1.setLongUrl(longUrl);
urlMap1.setShortUrl(shortLink);
urlMap1.setUsername(username);
urlMap1.setCreationTime(Instant.now());
urlMapMapper.saveUrlMap(urlMap1);


return shortLink;
}
}

// 解码
public String decode(String shortUrl){

// 根据短链接获取长链接
UrlMap byShortUrl = urlMapMapper.findByShortUrl(shortUrl);

// 如果存在,返回长链接
if(byShortUrl!=null){
return byShortUrl.getLongUrl();
}

// 如果没有,返回首页(正常是返回一个失败页面)
return "https://bilibili.com";

}

// 生成短链接
public static String generateShortLink(String originalUrl,String username) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hashBytes = md.digest(originalUrl.getBytes());
// 对原始URL进行MD5哈希计算

StringBuilder sb = new StringBuilder();
for (byte b : hashBytes) {
sb.append(String.format("%02x", b));
// 将字节数组转换为十六进制字符串
}

return sb.toString().substring(0, 8)+username;
// 截取前8位,加上用户名(这里先简单默认用户名是4位数)
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
}

5.Controller层实现

import com.zhan.zhan215.Common.ResponseBean;
import com.zhan.zhan215.Service.UrlMapService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;

import javax.annotation.Resource;

@RestController
public class UrlMapController {

@Resource
private UrlMapService urlMapService;

@PostMapping("/shorten")
// 长链接转短连接,相当于实际项目中的点击“分享”,形成一条短连接
public ResponseBean shorten(@RequestParam String longUrl,@RequestParam String username){

String encode = urlMapService.encode(longUrl,username);
// 形成短链

return ResponseBean.success(encode);

}

@GetMapping("redirect")
//重定向
public RedirectView redirectView(@RequestParam String shortUrl){

String longUrl = urlMapService.decode(shortUrl);

return new RedirectView(longUrl);
}
}

相关的ResponseBean的返回结果集代码:

public class ResponseBean<T> {

/** 200:操作成功 -1:操作失败**/

// http 状态码
private boolean success;

// 返回的数据
private T data;

public boolean isSuccess() {
return success;
}

public void setSuccess(boolean success) {
this.success = success;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

public static <T> ResponseBean<T> success(T data) {
ResponseBean<T> responseBean = new ResponseBean<>();
responseBean.setSuccess(true);
responseBean.setData(data);
return responseBean;
}

public static <T> ResponseBean<T> error(T errorData) {
ResponseBean<T> responseBean = new ResponseBean<>();
responseBean.setSuccess(false);
responseBean.setData(errorData);
return responseBean;
}

public static <T> ResponseBean<T> success() {
ResponseBean<T> responseBean = new ResponseBean<>();
responseBean.setSuccess(true);
return responseBean;
}

}

3.结果测试

我们就拿刚刚那个b站的长链接作测试,即https://www.bilibili.com/video/BV18u4m1K7D4/?spm_id_from=333.1007.tianma.10-4-38.click&vd_source=1c7e32cfbc70017a24ee2c337620ff51

可以看到短链接生成为1b9590bezhan,

然后我们再用这条链接去测试能否跳转到原始链接:

成功根据短链接定向到原始链接的网站了。

4.问题

为什么生成短链接需要带上username参数?

答:这里其实模仿的是不同用户对应同一个原始链接(或长链接)时,确保他们生成的短链接各不相同,这样可以方便后台追踪是由哪个用户分享的短链接,进而统计分享数。在表中LongUrl和shortUrl的对应关系为一对多

文章目录
  1. 1. 1.背景介绍
  2. 2. 2.短链跳转的意义
  3. 3. 3.SpringBoot中的代码实现
    1. 3.0.0.1. 1.建议短链-长链的数据库表:t_url_map:
    2. 3.0.0.2. 2.映射实体
    3. 3.0.0.3. 3.Dao层实现
    4. 3.0.0.4. 4.Service层实现
    5. 3.0.0.5. 5.Controller层实现
  • 4. 3.结果测试
  • 5. 4.问题