🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~
问题分析:为什么我们需要“短链”?
相信很多小伙伴日常生活中都收到过一些营销短信,里面附带了一些链接地址,是否发现链接实际上很短,点击后会再次访问一个相对长的链接。
例如我们收到的短链可能是这样子:
https://www.example.***/aBcDeF
点开它,会跳转到以下原始长链接(甚至更长…参数更多)
https://www.example.***/product/detail?id=134985&from=share&ref=longtext&utm_source=wechat
那就有小伙伴问了,博主博主我直接分享这个长链接不行么?为何多此一举通过短链条转长链?
那我们就来看看常见的短链应用场景:
- 社交平台分享:如微博、朋友圈、抖音简介中限制字符数;
- 短信营销:短链可以有效减少短信的字符数,从而减少短信费用;
- 营销追踪:每个渠道生成不同短链,统计访问量;
- 二维码应用:短链生成二维码更清晰、美观;
- 安全控制:短链服务可做访问限制、过期时间等策略。
系统设计分析
要实现一个简易的短链系统,我们至少需要解决以下问题:
| 模块 | 说明 |
|---|---|
| 短链生成策略 | 如何将长链接映射成短码? 并保证唯一性和随机性 |
| 存储设计 | 如何保存短码与原始链接的映射关系? |
| 重定向服务 | 访问短链时,如何高效跳转到原始链接? |
| 过期策略(可选) | 是否允许短链设置有效期? |
| 统计分析(可选) | 统计点击量、来源等指标。 |
如果你要实现一个更高性能的短链系统,你还需要引入缓存设计,如 redis , 本章节更多的是探讨短链系统,小伙伴们可以自行在业务层追加缓存来实现减少数据库的访问
数据库设计
这里我们只实现最核心的短链映射功能
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| short_code | varchar(10) | 短链编码(唯一索引) |
| original_url | varchar(500) | 原始长链接 |
| expire_time | datetime | 过期时间(可选) |
| create_time | datetime | 创建时间 |
SQL建表语句:
CREATE TABLE short_link (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
short_code VARCHAR(10) NOT NULL UNIQUE,
original_url VARCHAR(500) NOT NULL,
expire_time DATETIME NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
短链生成策略设计
短链生成核心是将长链接转换成唯一的短码,常见方式有:
- 自增ID + Base62编码 (简单可靠,唯一性高)
- 哈希算法(MD5/SHA)(可重复生成,长度可控)
自增ID + Base62编码 : Base62 使用
[0-9][a-z][A-Z]共 62 个字符编码,可生成短而唯一的字符串哈希算法(如 MD5 或 SHA-256)的主要作用是将一个长度不定的输入(如 URL)映射为固定长度的输出(哈希值)
实战开始
博主这里就讲解以上两种的实现方式
❶ 项目依赖
在正式开发前我们先在SpringBoot项目引入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>***.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
❷ 构建实体类
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "short_link")
public class ShortLink {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String shortCode;
@Column(nullable = false, length = 500)
private String originalUrl;
private LocalDateTime expireTime;
private LocalDateTime createTime = LocalDateTime.now();
}
❸ 工具类
Base62工具类 :使用自增ID + Base62编码
public class Base62Utils {
private static final String CHARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static String encode(long num) {
StringBuilder sb = new StringBuilder();
while (num > 0) {
int index = (int) (num % 62);
sb.append(CHARSET.charAt(index));
num /= 62;
}
return sb.reverse().toString();
}
}
哈希生成工具类(MD5/SHA):我们将实现一个工具类,通过 MD5 或 SHA-256 来生成哈希值,然后从中截取短链
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashUtils {
// 使用 MD5 算法生成短链
public static String generateHash(String input, String algorithm) {
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] hashBytes = md.digest(input.getBytes());
// 转换为 16 进制表示
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("哈希算法异常:" + e.getMessage());
}
}
// 使用 MD5 生成短链的前 8 位
public static String generateShortLink(String originalUrl) {
String md5Hash = generateHash(originalUrl, "MD5");
return md5Hash.substring(0, 8); // 返回前 8 位
}
// 使用 SHA-256 生成短链的前 10 位
public static String generateShortLinkSHA(String originalUrl) {
String sha256Hash = generateHash(originalUrl, "SHA-256");
return sha256Hash.substring(0, 10); // 返回前 10 位
}
}
❹ 服务层Service
为了更好的区分两种方式,这里我们将用两个Service来方便区分
Base62ShortLinkService
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class Base62ShortLinkService {
private final ShortLinkRepository repository;
@Transactional
public String createShortLink(String originalUrl) {
ShortLink entity = new ShortLink();
entity.setOriginalUrl(originalUrl);
ShortLink saved = repository.save(entity);
String shortCode = Base62Utils.encode(saved.getId());
saved.setShortCode(shortCode);
repository.save(saved);
return shortCode;
}
public String getOriginalUrl(String shortCode) {
ShortLink link = repository.findByShortCode(shortCode);
if (link == null) {
throw new RuntimeException("短链不存在或已失效");
}
return link.getOriginalUrl();
}
}
HashShortLinkService
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class HashShortLinkService {
private final ShortLinkRepository repository;
@Transactional
public String createShortLink(String originalUrl) {
// 先生成哈希值(使用 MD5 或 SHA-256)
String shortCode = HashUtils.generateShortLink(originalUrl);
// 检查数据库中是否已经存在该短链,避免重复
if (repository.findByShortCode(shortCode) != null) {
shortCode = HashUtils.generateShortLinkSHA(originalUrl); // 如果冲突,尝试用 SHA-256 生成短链
}
// 保存短链映射
ShortLink entity = new ShortLink();
entity.setOriginalUrl(originalUrl);
entity.setShortCode(shortCode);
repository.save(entity);
return shortCode;
}
public String getOriginalUrl(String shortCode) {
ShortLink link = repository.findByShortCode(shortCode);
if (link == null) {
throw new RuntimeException("短链不存在或已失效");
}
return link.getOriginalUrl();
}
}
❺ 控制层 Controller
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;
@RestController
@RequiredArgsConstructor
public class ShortLinkController {
private final Base62ShortLinkService base62ShortLinkService;
private final HashShortLinkService hashShortLinkService;
// 创建短链
@PostMapping("/api/shorten")
public String create(@RequestParam String url) {
String code = base62ShortLinkService.createShortLink(url);
//切换Hash
//String code = hashShortLinkService.createShortLink(url);
return "短链生成成功:" + "http://localhost:8080/" + code;
}
// 访问短链
@GetMapping("/{code}")
public RedirectView redirect(@PathVariable String code) {
String originalUrl = base62ShortLinkService.getOriginalUrl(code);
//切换Hash
//String originalUrl = hashShortLinkService.getOriginalUrl(code);
// 302重定向
return new RedirectView(originalUrl);
}
}
❻ 运行与测试
根据控制层的代码演示,小伙伴们可以自行切换两种实现方式,进行测试
以Hash为例生成短链接口:
POST http://localhost:8080/api/shorten?url=https://www.example.***/article/long-url
返回:
短链生成成功:http://localhost:8080/d41d8cd9
浏览器中打开短链,直接看条转效果!
总结
本文通过讲解了两种常见的短链生成方式:自增ID + Base62编码 和 哈希算法。并通过完整的简易示例代码,让小伙伴们可以快速理解并掌握短链系统的设计!
如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家 一键三连 给博主一点点鼓励!