在Java项目中构建短链接服务,核心要解决两个问题,一是如何将长链接映射为唯一的短链接标识,二是如何实现短链接到长链接的正确跳转。整个服务的设计需要考虑唯一性、性能、可扩展性等多个维度,下面逐步展开说明。

短链接生成逻辑
短链接生成的核心是建立长链接和短标识的一一映射关系,常见的生成方式有两种,分别是哈希算法生成和自增序列生成。
基于哈希算法的生成方式
这种方式通过对长链接进行哈希计算,再对哈希值进行编码得到短标识。常用的哈希算法有MD5、SHA-1等,编码方式可以选择Base62,避免生成包含特殊字符的标识。
实现步骤如下:
- 对原始长链接进行MD5哈希计算,得到32位的哈希字符串
- 将哈希字符串分割为若干段,取其中一段作为原始标识
- 将原始标识转换为Base62编码,得到最终的短链接标识
对应的Java代码示例如下:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class HashShortLinkGenerator {
// Base62字符集,不包含容易混淆的字符
private static final String BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/**
* 生成短链接标识
* @param longUrl 原始长链接
* @return 短链接标识
*/
public static String generateShortKey(String longUrl) {
try {
// 计算MD5哈希
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hashBytes = md.digest(longUrl.getBytes());
// 将哈希字节转换为十六进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
// 取前8位作为原始标识,转换为Base62
String rawKey = hexString.substring(0, 8);
return toBase62(rawKey);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("哈希算法不存在", e);
}
}
/**
* 将字符串转换为Base62编码
* @param input 输入字符串
* @return Base62编码结果
*/
private static String toBase62(String input) {
// 将字符串转换为字节数组后处理,简化逻辑
byte[] bytes = input.getBytes();
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
int value = b & 0xFF;
while (value > 0) {
result.append(BASE62_CHARS.charAt(value % 62));
value = value / 62;
}
}
return result.toString();
}
public static void main(String[] args) {
String longUrl = "https://ipipp.com/article/java-short-link-guide";
String shortKey = generateShortKey(longUrl);
System.out.println("长链接:" + longUrl);
System.out.println("短链接标识:" + shortKey);
}
}
基于自增序列的生成方式
这种方式需要维护一个全局自增的序列,每次生成短链接时获取当前序列值,再将其编码为短标识。这种方式的优点是生成的标识更短,且冲突概率极低,缺点是需要维护序列的状态,分布式场景下需要借助Redis、数据库等实现序列的自增。
基于Redis自增的实现代码示例:
import redis.clients.jedis.Jedis;
public class SequenceShortLinkGenerator {
private static final String BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private Jedis jedis;
// Redis中存储自增序列的key
private static final String SEQUENCE_KEY = "short_link_sequence";
public SequenceShortLinkGenerator(String redisHost, int redisPort) {
this.jedis = new Jedis(redisHost, redisPort);
}
/**
* 生成短链接标识
* @return 短链接标识
*/
public String generateShortKey() {
// 获取自增序列值
long sequence = jedis.incr(SEQUENCE_KEY);
return toBase62(sequence);
}
/**
* 将长整数转换为Base62编码
* @param num 输入的长整数
* @return Base62编码结果
*/
private String toBase62(long num) {
if (num == 0) {
return String.valueOf(BASE62_CHARS.charAt(0));
}
StringBuilder result = new StringBuilder();
while (num > 0) {
result.append(BASE62_CHARS.charAt((int) (num % 62)));
num = num / 62;
}
return result.reverse().toString();
}
public static void main(String[] args) {
SequenceShortLinkGenerator generator = new SequenceShortLinkGenerator("127.0.0.1", 6379);
String shortKey = generator.generateShortKey();
System.out.println("生成的短链接标识:" + shortKey);
}
}
短链接存储方案
生成短链接标识后,需要将短标识和长链接的映射关系存储起来,常见的存储方案有两种:
| 存储方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 关系型数据库(MySQL等) | 数据持久化可靠,支持复杂查询 | 读写性能相对较低,高并发下压力大 | 短链接数量不多,对数据可靠性要求高的场景 |
| Redis缓存 | 读写性能极高,支持原子操作 | 数据持久化需要额外配置,内存占用较高 | 高并发场景,对性能要求高的场景 |
实际生产中通常会采用Redis+MySQL的组合方案,短链接生成后先存入Redis,同时异步持久化到MySQL,查询时优先查Redis,未命中再查MySQL并回写Redis,兼顾性能和可靠性。
短链接跳转机制
短链接的跳转逻辑比较简单,当用户访问短链接时,服务端根据短标识查询对应的长链接,然后通过重定向返回给客户端即可。
基于Spring Boot的实现示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@RestController
public class ShortLinkController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 短链接在Redis中的前缀
private static final String SHORT_LINK_PREFIX = "short_link:";
/**
* 处理短链接跳转请求
* @param shortKey 短链接标识
* @return 重定向响应
*/
@GetMapping("/{shortKey}")
public ResponseEntity<Void> redirect(@PathVariable String shortKey) {
String longUrl = redisTemplate.opsForValue().get(SHORT_LINK_PREFIX + shortKey);
if (longUrl == null) {
// 短链接不存在,返回404
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
// 返回302临时重定向,跳转到长链接
return ResponseEntity.status(HttpStatus.FOUND)
.header("Location", longUrl)
.build();
}
}
这里使用302重定向而不是301重定向,是因为301重定向会被浏览器缓存,后续请求不会再发送到服务端,不利于统计短链接的访问次数,而302重定向每次都会请求服务端,方便做访问统计。
注意事项
- 短链接生成时需要校验长链接的合法性,避免生成指向恶意网站的短链接
- 可以设置短链接的有效期,过期后自动失效,减少存储占用
- 高并发场景下需要做好缓存预热和缓存击穿的防护,比如对不存在的短标识做空值缓存
- 如果需要统计短链接的访问数据,可以在跳转逻辑中增加访问计数、记录访问时间等操作