简介
我们日常在收短信的时候,会经常发现,他们的短信里的跳转链接,都是非常短非常短的,这是为什么呢?
因为现在短信新规出来,是按字数收费的,而不是按条。比如腾讯云短信新规,70字为一条,哪怕你是一条短信,100个字,就会按照两条来计费,这个时候短链接就有了相当大的优势。那么短链接是如何实现的呢?
短链接原理
其实就是在后台保存有短链和长链的映射关系,然后进行重定向,让浏览器跳转到对应的长链接。首先访问短链接,根据短链接查询数据库获取完整长链接,返回301或者302,让浏览器重定向到目标地址,浏览器跳转到长链接。
例子:当访问短链接,https://域名/xxx时,后端返回了302,同时多了一个Location响应头,值就是原始链接地址
解决方案
第一种是对 URL 进行hash运算,得到较短的hash值,Murmur哈希就是其中之一。既然是通过哈希函数,就避免不了哈希冲突。虽然这概率很低,但我们设计系统时需要考虑。
第二种对数据存储,由短链接跳转到长链接,肯定有必然的关系,我们需要把他们保存起来,存储的方式有Redis、Mysql等数据库。
例子:如果是Mysql存储,表结构大概有(自增id、短链接、长链接、创建时间)这些字段;我们可以通过生成的短链接,查询数据库是否有存在的短链接。不存在,直接存储;存在,需要二次拼接生成短链接,直到不存在为止,进行存储。如果当数据库数据变多,我们需要优化,短链接字段需要加唯一性索引。如果再一次优化,可以使用布隆过滤器,将新生成的短链接在布隆过滤器里进行查找,不存在直接插入数据库。
生成短链接方法
- 根据唯一自增id后,再转换为62进制字符串,生成短链接。优点:ID唯一,生成的短链不会重复和冲突;缺点:高并发下有性能瓶颈。
- 雪花算法。优点:高性能;缺点:生成的ID比较长。
- Redis自增。优点:高性能,高并发;缺点:是中间件,有维护成本。
- Hash算法,常见有MD5、SHA算法。但这里我们一般采用Google的Murmurhash算法,优点:哈希冲突的概率低,速度比MD5快,缺点:有几率哈希冲突
建表
CREATE TABLE `url_convert` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`longUrl` text,
`shortUrl` varchar(256) DEFAULT NULL,
`userId` int(11) DEFAULT '0',
`count` int(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
转短链接并加密
public static String getShortURL(String URL) {
// 可以自定义生成 MD5 加密字符传前的混合 KEY
String key = "QDGYJ";
// 要使用生成 URL 的字符
String[] chars = new String[]{"a", "b", "c", "d", "e", "f", "g", "h",
"i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
"u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
"U", "V", "W", "X", "Y", "Z"
};
// 对传入网址进行 MD5 加密
String hex = md5ByHex(key + URL);
String[] resUrl = new String[4];
for (int i = 0; i < 4; i++) {
// 把加密字符按照 8 位一组 16 进制与 0x3FFFFFFF 进行位与运算
String sTempSubString = hex.substring(i * 8, i * 8 + 8);
// 这里需要使用 long 型来转换,因为 Inteper .parseInt() 只能处理 31 位 , 首位为符号位 , 如果不用long ,则会越界
long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16);
String outChars = "";
for (int j = 0; j < 6; j++) {
// 把得到的值与 0x0000003D 进行位与运算,取得字符数组 chars 索引
long index = 0x0000003D & lHexLong;
// 把取得的字符相加
outChars += chars[(int) index];
// 每次循环按位右移 5 位
lHexLong = lHexLong >> 5;
}
// 把字符串存入对应索引的输出数组
resUrl[i] = outChars;
}
Random random = new Random();
int j = random.nextInt(4);
//随机取一个作为短链
return resUrl[j];
}
/**
* MD5加密(32位大写)
*
* @return List<Map < String, Object>>
* @author DingYongJun
* @date 2020/8/18
*/
public static String md5ByHex(String src) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] b = src.getBytes();
md.reset();
md.update(b);
byte[] hash = md.digest();
String hs = "";
String stmp = "";
for (int i = 0; i < hash.length; i++) {
stmp = Integer.toHexString(hash[i] & 0xFF);
if (stmp.length() == 1)
hs = hs + "0" + stmp;
else {
hs = hs + stmp;
}
}
return hs.toUpperCase();
} catch (Exception e) {
return "";
}
}
重定向
@GetMapping("/getShortUrl")
@CrossOrigin
public ResultData<String> getShortUrl(String longUrl) {
String shortUrl = URLUtils.getShortURL(longUrl);
convertService.add(shortUrl, longUrl);
return ResultData.success(RequestCode.SUCCESS.getCode(), "可以使用", shortUrl);
}
@GetMapping("/{shortUrl}")
@CrossOrigin
public RedirectView redirectView(@PathVariable String shortUrl) {
saveArticleService.addCount(shortUrl);
WxArticle longUrl = saveArticleService.getLongUrl(shortUrl);
return new RedirectView(longUrl.getMediumUrl());
}
getShortUrl 接口,是把长连接转为短链接,获取短链接的数据,然后拿到短链接数据后,调用重定向接口,重定向到长连接。