什么是短网址(短链接)?如图
用处:
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>
全文完,感谢您的耐心阅读~
欢迎大家关注我的公众号