什么是短网址(短链接)?如图
用处:
1、短信营销
2、网站留言
3、生成二维码
….
百度云,阿里云,腾讯云现在基本上都提供这个服务,本文介绍使用java实现自己的短链接服务。
短链接看似很复杂,透过现象看本质,你会发现它的核心就是分布式ID唯一算法。
首先你需要一个够短且外网可以访问的域名,比如t.com , a.cn ,…。本文使用的短域名是tt.cn ,我直接修改windows host文件 指向127.0.0.1
关于短链接的算法,我也看了网上其它人写的文章,总体来说有两种
1、基于MD5码
2、自增序列
在介绍这两种算法之前,我们要关注,效率和安全两者的关系。太复杂的算法保证安全一般意味着效率的降低,而根据实际业务,放出去的短链接其实并不需要过多的加密保证规范即可。相反,在访问短链接的时候能够快速寻到原链接这个才是最重要的。
这两种算法逻辑区别:
1、MD5码生成的短链接可能存在碰撞(概率很小)
2、自增序列需要借助第三方发号器(数据库自增,雪花算法,redis incr)…
本文采取的方案是基于google,Hashing + 62进制来完成
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
public static void main(String[] args){
String url1="http://www.weather.com.cn/wzfw/ryzp/index.shtml";
String url2="http://www.gov.cn/xinwen/2020-09/17/content_5544097.htm";
String url3="http://www.zj.gov.cn/art/2020/9/17/art_1554467_57705747.html";
String url4="http://www.moe.gov.cn/jyb_xwfb/gzdt_gzdt/s5987/202009/t20200917_488442.html";
System.out.println(shortUrl(url1));
System.out.println(shortUrl(url2));
System.out.println(shortUrl(url3));
System.out.println(shortUrl(url4));
}
public static Object shortUrl(String url){
//google的Hashing将实际网址hash之后转为long
long s = Hashing.murmur3_32().hashUnencodedChars(url).padToLong();
System.out.println(s);
//将10进制转为62进制
String encode = ShortLinkUtils.base62Encode(s);
return encode;
}
public class ShortLinkUtils {
private static String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static int scale = 62;
private static int minLength = 6;
/**
* 将10进制数字转为62进制
*
* @param num Long 型数字
* @return 62进制字符串
*/
public static String base62Encode(long num) {
StringBuilder sb = new StringBuilder();
int remainder;
while (num > scale - 1) {
remainder = Long.valueOf(num % scale).intValue();
sb.append(chars.charAt(remainder));
num = num / scale;
}
sb.append(chars.charAt(Long.valueOf(num).intValue()));
String value = sb.reverse().toString();
return StringUtils.leftPad(value, minLength, '0');
}
/**
* 62进制字符串转回十进制数字
*
* @param str 编码后的62进制字符串
* @return 解码后的 10 进制字符串
*/
public static long base62Decode(String str) {
str = str.replace("^0*", "");
long num = 0;
int index = 0;
for (int i = 0; i < str.length(); i++) {
index = chars.indexOf(str.charAt(i));
num += (long) (index * (Math.pow(scale, str.length() - i - 1)));
}
return num;
}
}
这就是一个简单且可用的短链接生成算法,眼尖的小伙伴可能看出来了,ShortLinkUtils#base62Decode,这个反解析的方法是干什么的呢?
假如,你根据mysql自增主键生成短链接,id=100098 ShortLinkUtils#base62Encode 得到短码:N87ja9 。那么ShortLinkUtils#base62Decode 解析N87ja9 得到 100098 ,但是并不建议这样做,因为反解析需要耗费性能,这个算法的时间复杂度是O(n)级别,且算法不能变动,一旦变动前期发出去的短链接无法解析或者需要做适配,增大工作量。因此我们直接用生成的短码映射实际的长链接即可。
全部代码如下:
public class ShortLinkUtils {
private static String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static int scale = 62;
private static int minLength = 6;
/**
* 将10进制数字转为62进制
*
* @param num Long 型数字
* @return 62进制字符串
*/
public static String base62Encode(long num) {
StringBuilder sb = new StringBuilder();
int remainder;
while (num > scale - 1) {
remainder = Long.valueOf(num % scale).intValue();
sb.append(chars.charAt(remainder));
num = num / scale;
}
sb.append(chars.charAt(Long.valueOf(num).intValue()));
String value = sb.reverse().toString();
return StringUtils.leftPad(value, minLength, '0');
}
/**
* 62进制字符串转为十进制数字
*
* @param str 编码后的62进制字符串
* @return 解码后的 10 进制字符串
*/
public static long base62Decode(String str) {
str = str.replace("^0*", "");
long num = 0;
int index = 0;
for (int i = 0; i < str.length(); i++) {
index = chars.indexOf(str.charAt(i));
num += (long) (index * (Math.pow(scale, str.length() - i - 1)));
}
return num;
}
}
public class ShortLinkCommon {
/**
* 直接修改 windows hosts tt.cn 127.0.0.1
*/
public static final String DOMAIN_PREFIX = "tt.cn";
/**
* 存储短链接码->原始链接 映射关系,生产环境不可如此使用
*/
public static Map<String, String> shortLinkMap = new HashMap<>();
}
@Controller
public class ShortController {
/**
* {s} 可以使用26个英文字母,划分不同的业务
*
* @param shortLinkCode 短链接code
* @param response
* @throws IOException
*/
@GetMapping("{s}/{shortLinkCode}")
public void redirect(@PathVariable("shortLinkCode") String shortLinkCode, HttpServletResponse response) throws IOException {
String fullLink = ShortLinkCommon.shortLinkMap.get(shortLinkCode);
response.sendRedirect(fullLink);
}
}
@RestController
@RequestMapping("short")
public class ShortLinkController {
/**
* create shortLinkCode
*
* @param url 原始链接
* @return
*/
@PostMapping("create")
public String create(String url) {
long num = Hashing.murmur3_32().hashUnencodedChars(url).padToLong();
String shortLinkCode = ShortLinkUtils.base62Encode(num);
ShortLinkCommon.shortLinkMap.put(shortLinkCode, url);
return ShortLinkCommon.DOMAIN_PREFIX.concat("/a").concat("/").concat(shortLinkCode);
}
}
@SpringBootApplication
public class ShortLinkApplication {
public static void main(String[] args) {
SpringApplication.run(ShortLinkApplication.class, args);
}
}
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>24.0-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>