1.背景介紹
相信很多人手機上都收到過一些營銷短信,短信里面有時候會附帶一些網址,如下圖

這些網址往往都是非常短,就是我們常說的短網址(短域名)。所以短網址(短域名)服務就是把普通網址,轉換成比較短的網址。在微博以及短信這些限制字數的應用里。好處不言而喻。短、字符少、美觀、便於發布、傳播。
2.原理和應用
其實很簡單 ,系統把一個長的地址 如 http://www.xxx.com/ddd/xxx/a.html?dsada
首先用一個算法轉換成 短地址 http://t.cn/Afafhe
然后把 Afafhe-->http://www.xxx.com/ddd/xxx/a.html?dsada 的關系保存下來
當用戶訪問 http://t.cn/Afafhe網址時,系統找到對應的原來的長URL地址,實現跳轉
3.Java簡單實現的代碼
import com.daqsoft.common.constant.Constants;
import java.security.MessageDigest;
import java.util.Random;
/**
* 短連接生成工具
*
* @author fanhj
* @version V1.0.0
* @date 2020-09-09
*/
public class ShortUrlUtils {
/**
* 會使用到的常量
*/
public static final long LONG_0X3FFFFFFF = 0x3FFFFFFF;
public static final long LONG_0X0000003D = 0x0000003D;
public static final int LONG_0XFF = 0xFF;
public static void main(String[] args) {
// 原始鏈接
String sLongUrl = "http://www.51bi.com/bbs/_t_278433840/";
System.out.println("長鏈接:" + sLongUrl);
// 將產生4組6位字符串
String[] aResult = shortUrl(sLongUrl);
// 打印出結果
for (int i = Constants.INDEX_0; i < aResult.length; i++) {
System.out.println("[" + i + "]:" + aResult[i]);
}
Random random = new Random();
// 生成4以內隨機數
int j = random.nextInt(Constants.INDEX_4);
// 隨機取一個作為短鏈
System.out.println("短鏈接:" + aResult[j]);
}
public static String[] shortUrl(String url) {
// 可以自定義生成 MD5 加密字符傳前的混合 KEY
String key = "jkb66.com";
// 要使用生成 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[Constants.INDEX_4];
for (int i = Constants.INDEX_0; i < Constants.INDEX_4; i++) {
// 把加密字符按照 8 位一組 16 進制與 0x3FFFFFFF 進行位與運算
String sTempSubString = hex.substring(i * Constants.INDEX_8, i * Constants.INDEX_8 + Constants.INDEX_8);
// 這里需要使用 long 型來轉換,因為 Integer.parseInt() 只能處理 31 位 , 首位為符號位 ,如果不用long,則會越界
long lHexLong = LONG_0X3FFFFFFF & Long.parseLong(sTempSubString, Constants.INDEX_16);
String outChars = "";
for (int j = Constants.INDEX_0; j < Constants.INDEX_6; j++) {
// 把得到的值與 0x0000003D 進行位與運算,取得字符數組 chars 索引
long index = LONG_0X0000003D & lHexLong;
// 把取得的字符相加
outChars += chars[(int) index];
// 每次循環按位右移 5 位
lHexLong = lHexLong >> Constants.INDEX_5;
}
// 把字符串存入對應索引的輸出數組
resUrl[i] = outChars;
}
return resUrl;
}
/**
* MD5加密(32位大寫)
*
* @param src 原始字符串
* @return 加密之后的字符串
*/
private static String md5ByHex(String src) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] b = src.getBytes();
md.reset();
md.update(b);
byte[] hash = md.digest();
StringBuffer buffer = new StringBuffer();
String stmp = "";
for (int i = Constants.INDEX_0; i < hash.length; i++) {
stmp = Integer.toHexString(hash[i] & LONG_0XFF);
if (stmp.length() == Constants.INDEX_1) {
buffer.append("0" + stmp);
} else {
buffer.append(stmp);
}
}
return buffer.toString().toUpperCase();
} catch (Exception e) {
return "";
}
}
}
4.Java解析短網址解析與跳轉
import com.alibaba.fastjson.JSONObject;
import com.daqsoft.common.constant.Constants;
import com.daqsoft.common.utils.DateUtil;
import com.daqsoft.framework.support.AbstractCommonController;
import com.daqsoft.framework.support.DataResponse;
import com.daqsoft.zytf.constant.ZytfConstant;
import com.daqsoft.zytf.utils.ShortUrlUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 短連接請求接受處理Controller
*
* @author fanhj
* @version 1.0.0
* @date 2020-09-09
* @since JDK 1.8
*/
@RestController
@RequestMapping("/push")
@CrossOrigin(allowCredentials = "true")
public class PushShortUrlController extends AbstractCommonController {
@Autowired
private RedisTemplate redisTemplate;
/**
* 前端調用,將原來前端自己拼接成的下載鏈接地址,轉為短連接返回到前端
*
* @param request request請求對象
* @param longUrl 原來前端自己拼接成的下載鏈接地址
* @return 最近一周數據倒序顯示
*/
@RequestMapping("/getShortUrl")
@ResponseBody
public DataResponse getShortUrl(HttpServletRequest request, String longUrl) {
String server = "http://" + request.getServerName();
String[] shortUrlArray = ShortUrlUtils.shortUrl(longUrl);
String shortUrl = server + "/push/" + shortUrlArray[Constants.INDEX_4];
ValueOperations<String, Map> opsForValue = redisTemplate.opsForValue();
Map<String, Object> map = new HashMap<String, Object>(Constants.INDEX_4);
// 緩存的短連接 對應的原始鏈接
map.put("longUrl", longUrl);
// 緩存的短連接的創建時間
map.put("createTime", DateUtil.getCurrentDateTime());
// 緩存的短連接的字符串內容
map.put("shortUrl", shortUrl);
// 緩存7天 也可以在此記錄短網址的點擊量等數據
opsForValue.set("short_url_" + shortUrl, map, Constants.INDEX_7, TimeUnit.DAYS);
JSONObject resultJson = new JSONObject();
resultJson.put("shortUrl", shortUrl);
return succeed(resultJson);
}
/**
* 短連接請求接受處理處
*
* @param id 短連接內容
* @param request request請求對象
* @param response response輸出對象
* @throws IOException 拋出的異常
*/
@RequestMapping(value = "/{id}")
public void url(@PathVariable String id, HttpServletRequest request,
HttpServletResponse response) throws IOException {
//獲得客戶端發送請求的完整url 即我們的短連接
String shortUrlStr = request.getRequestURL().toString();
ValueOperations<String, Map> opsForValue = redisTemplate.opsForValue();
Map<String, Object> map = opsForValue.get("short_url_" + shortUrlStr);
if (map == null) {
response.sendError(ZytfConstant.INDEX_404, "鏈接已過期失效");
return;
}
String longUrl = map.get("longUrl").toString();
if (StringUtils.isEmpty(longUrl)) {
response.sendError(ZytfConstant.INDEX_404, "鏈接已過期失效");
return;
} else {
response.sendRedirect(longUrl);
return;
}
}
}
