其他
如何实现 "长"链接变 "短" 链接?
脚本之家
你与百万开发者在一起
本文经由博客园作者 rickiyang 授权转载
原文地址:www.cnblogs.com/rickiyang/p/12178644.html
如需转载请联系原作者
将长链接变为短链; 用户访问短链接,会跳转到正确的长链接上去。
短链生成方法
[a - z, A - Z, 0 - 9]
这 62 个字母或数字组成,短码的长度也可以自定义,但一般不超过 8 位。比较常用的都是 6 位,6 位的短码已经能有 568 亿种的组合:(26+26+10)^6 = 56800235584,已满足绝大多数的使用场景。自增id
、摘要算法
、普通随机数
。自增 id
摘要算法
将长网址 md5 生成 32 位签名串,分为 4 段, 每段 8 个字节; 对这四段循环处理, 取 8 个字节, 将他看成 16 进制串与 0x3fffffff(30 位 1)与操作, 即超过 30 位的忽略处理; 这 30 位分成 6 段, 每 5 位的数字作为字母表的索引取得特定字符, 依次进行获得 6 位字符串; 总的 md5 串可以获得 4 个 6 位串;取里面的任意一个就可作为这个长 url 的短 url 地址;
普通随机数
Math.round()
方法生成的随机数属于伪随机数,碰撞的可能性也不小。在数据比较多的情况下,可能会循环很多次,才能生成一个不冲突的短码。实现
存储方案
base_url | suffix_url | shot_code | total_click_count | full_url | expiration_date |
查询需求
新增需求
缓存的设计
DNS 首先解析获得 http://bit.ly的 IP
地址当 DNS
获得IP
地址以后(比如:12.34.5.32),会向这个地址发送HTTP
GET
请求,查询短码a3300
[http://bit.ly 服务器会通过短码 a3300
获取对应的长 URL请求通过 HTTP
301
转到对应的长 URL http://www.theaustralian.news.com.au/story/0,25197,26089617-5013871,00.html。
301 是永久重定向,302 是临时重定向。短地址一经生成就不会变化,所以用 301 是符合 http 语义的。但是如果用了 301, Google,百度等搜索引擎,搜索的时候会直接展示真实地址,那我们就无法统计到短地址被点击的次数了,也无法收集用户的 Cookie, User Agent 等信息,这些信息可以用来做很多有意思的大数据分析,也是短网址服务商的主要盈利来源。 引自知乎-武林的回:https://www.zhihu.com/question/20103344/answer/573638467
import org.apache.commons.lang3.StringUtils;
import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicLong;
import static com.alibaba.fastjson.util.IOUtils.DIGITS;
/**
* @author rickiyang
* @date 2020-01-07
* @Desc TODO
*/
public class ShortUrlGenerator {
public static void main(String[] args) {
String sLongUrl = "http://www.baidu.com/121244/ddd";
for (String shortUrl : shortUrl(sLongUrl)) {
System.out.println(shortUrl);
}
}
public static String[] shortUrl(String url) {
// 可以自定义生成 MD5 加密字符传前的混合 KEY
String key = "dwz";
// 要使用生成 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 sMD5EncryptResult = "";
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update((key + url).getBytes());
byte[] digest = md.digest();
sMD5EncryptResult = DatatypeConverter.printHexBinary(digest).toUpperCase();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
String[] resUrl = new String[4];
//得到 4组短链接字符串
for (int i = 0; i < 4; i++) {
// 把加密字符按照 8 位一组 16 进制与 0x3FFFFFFF 进行位与运算
String sTempSubString = sMD5EncryptResult.substring(i * 8, i * 8 + 8);
// 这里需要使用 long 型来转换,因为 Inteper .parseInt() 只能处理 31 位 , 首位为符号位 , 如果不用 long ,则会越界
long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16);
String outChars = "";
//循环获得每组6位的字符串
for (int j = 0; j < 6; j++) {
// 把得到的值与 0x0000003D 进行位与运算,取得字符数组 chars 索引(具体需要看chars数组的长度 以防下标溢出,注意起点为0)
long index = 0x0000003D & lHexLong;
// 把取得的字符相加
outChars += chars[(int) index];
// 每次循环按位右移 5 位
lHexLong = lHexLong >> 5;
}
// 把字符串存入对应索引的输出数组
resUrl[i] = outChars;
}
return resUrl;
}
}
/**
* @author rickiyang
* @date 2020-01-07
* @Desc TODO
* <p>
* 进制转换工具,最大支持十进制和62进制的转换
* 1、将十进制的数字转换为指定进制的字符串;
* 2、将其它进制的数字(字符串形式)转换为十进制的数字
*/
public class NumericConvertUtils {
public static void main(String[] args) {
String str = toOtherNumberSystem(22, 62);
System.out.println(str);
}
/**
* 在进制表示中的字符集合,0-Z分别用于表示最大为62进制的符号表示
*/
private static final char[] digits = {'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',
'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'};
/**
* 将十进制的数字转换为指定进制的字符串
*
* @param number 十进制的数字
* @param seed 指定的进制
* @return 指定进制的字符串
*/
public static String toOtherNumberSystem(long number, int seed) {
if (number < 0) {
number = ((long) 2 * 0x7fffffff) + number + 2;
}
char[] buf = new char[32];
int charPos = 32;
while ((number / seed) > 0) {
buf[--charPos] = digits[(int) (number % seed)];
number /= seed;
}
buf[--charPos] = digits[(int) (number % seed)];
return new String(buf, charPos, (32 - charPos));
}
/**
* 将其它进制的数字(字符串形式)转换为十进制的数字
*
* @param number 其它进制的数字(字符串形式)
* @param seed 指定的进制,也就是参数str的原始进制
* @return 十进制的数字
*/
public static long toDecimalNumber(String number, int seed) {
char[] charBuf = number.toCharArray();
if (seed == 10) {
return Long.parseLong(number);
}
long result = 0, base = 1;
for (int i = charBuf.length - 1; i >= 0; i--) {
int index = 0;
for (int j = 0, length = digits.length; j < length; j++) {
//找到对应字符的下标,对应的下标才是具体的数值
if (digits[j] == charBuf[i]) {
index = j;
}
}
result += index * base;
base *= seed;
}
return result;
}
}
END
更多精彩
在公众号后台对话框输入以下关键词
查看更多优质内容!
女朋友 | 大数据 | 运维 | 书单 | 算法
大数据 | JavaScript | Python | 黑客
AI | 人工智能 | 5G | 区块链
机器学习 | 数学 | 送书