Java 網址短鏈接服務原理及解決方案


一、背景

       現在在各種圈的產品各種推廣地址,由於URL地址過長,不美觀、不方便收藏、發布、傳播以及各種發文字數限制等問題,微信、微博都在使用短鏈接技術。最近由於使用的三方的生成、解析短鏈接服務開始限制使用以及准備收費、不方便統計分析、不方便流控等問題,決定自建一個短地址服務。

二、原理

     比如,http://a.b.com/15uOVS 這個短地址

     第1步,瀏覽器請求這個地址

     第2步,通過DNS后到短地址服務端,還原這個短地址對應的原始長地址。

     第3步,請求http 301 或302到原始的長地址上

     第4步,瀏覽器拿到原始長地址的響應response

三、實現

     短地址服務的核心是短地址和長地址的轉化映射算法。

     最簡單的算法是把原來的長地址做MD5摘要記為key,長地址記為value。把key value放入服務端緩存中比如redis中。

     反向解析時通過URL解決出key來,比如上面的短地址key = 15uOVS 。然后通過key去緩存中獲取原始長地址value實現URL地址還原。

     MD5摘要有幾個明顯的問題:

     1、短地址的長度受限,比如MD5后的數據長度是32位,需要進行分段循環處理,使短地址足夠短

     2、MD5的哈希碰撞問題,有一定的概率重復,解決此問題,需要不斷的提升算法的復雜度,有些得不償失

     當然不止MD5實現算法比較多,大家可以自行谷歌。

    

    筆者的實現原理如下:

    1、新建一張表,主要字段包括自增ID【這里有個小技巧,可以給自增ID設置一個漂亮的起步值,使短地址碼隨機性更好一些,不建議從0開始自增,比如alter table mapping_info  AUTO_INCREMENT=1112345612;】,原始長地址URL等,做一個自增的10進制長整型短地址key,然后把key轉化成62進制,映射到0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz這62個字符上,做成6位的短鏈接碼,如開頭的例子15uOVS,【 6位62進制數,對應的號碼空間為62的6次方,約等於568億,即可以生成586億的短網址,基本能滿足需求】。

    2、把短地址碼及長地址,通過key value的形式放入redis和數據庫中。

    3、寫一個過濾器ShortUrlMappingFilter 過濾器(為什么是過濾器,因為過濾器是對請求進行過濾轉發的,不明白自己谷歌)的執行order設為第一個執行,即值最小。

注: 攔截器、過濾器、監聽器的執行順序    --->   監聽器 > 過濾器 > 攔截器 > controller執行 > 攔截器 > 過濾器 > 監聽器

相關核心代碼如下:

(一) 、ShortUrlMappingFilter過濾器如下:

 /**
* @author xuzhujack
**/
public class ShortUrlMappingFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(ShortUrlMappingFilter.class);
private static final int shortUrlKeyLength = 6;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
    LOGGER.info("ShortUrlMappingFilter init ...........");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
   LOGGER.info("ShortUrlMappingFilter url mapping start......");
   HttpServletRequest request = (HttpServletRequest) servletRequest;
   HttpServletResponse response = (HttpServletResponse) servletResponse;
   BeanFactory factory = WebApplicationContextUtils
   .getRequiredWebApplicationContext(request.getServletContext());
   ShortUrlService shortUrlService = (ShortUrlService) factory.getBean("shortUrlService");

  if(request.getRequestURI().substring(1).trim().length()== shortUrlKeyLength){
     ShortUrlDto query = new ShortUrlDto();
     query.setId(ShortUrlUtils.base62Decode(request.getRequestURI().substring(1).trim()));
     ShortUrl shortUrl = shortUrlService.queryShortUrl(query);
     if (shortUrl == null) {
       LOGGER.error("ShortUrlMappingFilter shortUrl is null ...........dto is {}", query);
       throw new ServletException();
     } else {
       String srcUrl = shortUrl.getSrcUrl();
       LOGGER.info("ShortUrlMappingFilter shortUrl is not null .shortUrl is {}...........dto is {}", shortUrl, query);
       response.sendRedirect(srcUrl);
    }
  }else{
   filterChain.doFilter(request, response);
   LOGGER.info("非短鏈接正常后續的doFilter..........");
  }

}

    @Override
    public void destroy() {

   }
}

   (二)、 ShortUrlMappingFilter在項目的啟動主類中增加如下配置:

 

@Bean
public FilterRegistrationBean contextFilterRegistrationBean2() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new ShortUrlMappingFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setName("ShortUrlMappingFilter");
registrationBean.setOrder(0);
return registrationBean;
}

 

     (三)、62進制和10進制的相互轉換

/**
* 將數字轉為62進制
* @param num Long 型數字
* @return 62進制字符串
*/

public static String base62Encode(long num) {
String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
StringBuilder sb = new StringBuilder();
int remainder = 0;
int scale = 62;
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, 6, '0');
}

/**
* 62進制字符串轉為數字
* @param str 編碼后的62進制字符串
* @return 解碼后的 10 進制字符串
*/
public static long base62Decode(String str) {
String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int scale = 62;
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;
}

 

 

 

     

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM