一 短链接介绍
举个栗子:
有时会收到和上面差不多的营销短信,e9q.cn/CeCKrn这个就是短连接。
他是怎么生成的呢?
可以网上找一个短域名服务商,例如urlc.cn
现在我的掘金技术社区的地址是这个:https://juejin.cn/user/26044011644071/posts (45个字符)
我通过的短链接服务https://www.urlc.cn/可以将上面的地址转成http://k8i.cn/b6lQY(19个字符)
为什么要生成短连接
- 看个连接
https://search.jd.com/Search?keyword=%E4%BB%8E%E4%BD%A0%E7%9A%84%E5%85%A8%E4%B8%96%E7%95%8C%E8%B7%AF%E8%BF%87&enc=utf8&suggest=2.def.0.SAK7|MIXTAG_SAK7R,SAK7_M_AM_L5361,SAK7_M_COL_L15275,SAK7_S_AM_R,SAK7_SC_PD_R,SAK7_SS_PM_LC|G*SAK7_M_COL_L15275&wq=%E7%A9%BF%E8%BF%87&pvid=955b57b84dee41f3b1d0b33f13db34fe
这是个电商的链接,非常长。
一般单条短信的字数限制的70字。
上面的连接要作为营销链接,可能要发送好几条。用户也不会习惯去拼接着写字符串再访问这个连接。
- 怎么办呢 我们可以将原来的长链接转成较短的链接。
二 短链接设计
我们先回到生成好的短链上http://k8i.cn/b6lQY
虽然这个链接看起来有点奇怪,但他终究还是一个链接,从URL的特征我们可以分出:
- k8i.cn是域名
b6lQY是参数。
短链接的原理其实就是:
- 将长链接通过一定的
手段生成一个短链接 - 访问短链接时实际访问的是短链接服务器,然后根据短链接的参数找回对应的长链接
- 重定向跳转
通过上面的分析我们可以知道的是,我们实际核心要做的是怎么从b6lQY类似这样的参数找到对应的完整URL:https://juejin.cn/user/26044011644071/posts
脑子第一时间想到的是:能不能通过一个压缩算法将https://juejin.cn/user/26044011644071/posts压缩更小的字符?
- md5加密链接再截取指定长度作为短链的
path
url = hashlib.new('md5', bytes(link, "utf-8")).hexdigest()[0:5]
这种方式会有个问题就是哈希冲突,因为你无法加密后保证截取的五个字符是否是一样的。
- 优化方案
-
ID自增后,转成62进制(10个数字+26小写英文字母+26大写英文字母),在DB保存映射关系,生成短链接
-
5位数的短地址数量931,151,402.
-
6位数的短地址数量558亿 过程描述
-
1 长链接进入
-
2 签发ID 0-500亿 eg:
id = 13333333 -
3 将ID转换成 62进制 eg: 62进制转换后
path=3A3n -
4 短链的
path值为62进制的3A3n -
5 返回短链
http://ddz.com/3A3n
-
id 0 62进制 0 url http://aaa.com
id 62 62进制 Z url http://bbb.com
id 14332343333 62进制 4FSsxjh url http://ccc.com
这样就可以解决hash冲突问题了。
新问题:信息安全
假设当前PATH=F4VNe0自增后依次为PATH=F4VNe1 PATH=F4VNe2 PATH=F4VNe3 ... ... PATH=F4VNex PATH=F4VNey PATH=F4VNez ... ... PATH=F4VNeX PATH=F4VNeY PATH=F4VNeZ
自增的ID可能比较容易猜出来规律,从而造成数据泄露。
场景:AB是付费短连接服务商,因为A服务商的短连接是自增的,被B供应商猜到了。通过自增的短连接访问到了客户真实的地址,可以采用合理的营销方式撬走你的客户。所以短链也有加密需求。
压缩算法优化
-
固定长度
path6位,所以id区间[931,151,402, 558亿]之间自增 -
62进制 右移一位 通过62进制生成的
path=4VNe5F,右移一位以后path >> path=F4VNe5 -
在指定位置插入
- 链接md5第一位
=e,插入path的第2个位置,path=Fe4VNe5
- 链接md5第一位
部分代码
main.html
// 全部代码在gitee上,底下有git地址
<div id="container" class="center-justify">
<div class="sea-container">
<form action="/" method="post">
<input type="text" name="url" class="blue-input">
<input type="submit" value="生成" class="blue-button">
</form>
<p id="selectedId">端地址:{{url}}</p>
</div>
</div>
index.html
//页面跳转
<script>window.location.href="{{url}}";</script>
script.py
def binary_conversion(num, bc_start, bc_end):
# num位数字,bc_start为num的进制数,bc_end为目标进制数,取值为2-62
a = ['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']
nx = str(num)
b1 = list(nx)
b2 = []
for i in b1:
for i1 in range(0, 62):
if a[i1] == i:
b2 = b2 + [i1]
if i1 > bc_start:
print(i, "错误定义")
b2.reverse()
n1 = 0
n2 = 1
for i in b2:
n1 = n1 + int(i) * (pow(bc_start, n2 - 1))
n2 = n2 + 1
n = n1
b = []
while True:
s = n // bc_end
y = n % bc_end
b = b + [y]
if s == 0:
break
n = s
b.reverse()
bd = ""
for i in b:
bd = bd + a[i]
return bd
def is_http(url):
# 判断网址的合法性
result = re.findall(
r"(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*,]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)|([a-zA-Z]+.\w+.+[a-zA-Z0-9/_]+)",
url)
return True if result else False
def left_str(s):
lens = len(s)
return s[lens - 2:] + s[0:lens - 2] # 左移两位
def right_str(s):
return s[2:] + s[:2] # 右移2位
def get_url_number():
# 签发url的id
try:
num = res_obj.get("url_number")
num = int(num) + 1 # 62进制,每次加1 可用地址500多亿
res_obj.set("url_number", num)
except:
num = 1000000000
res_obj.set("url_number", num)
return num
view.py
@app.route('/', methods=["GET", "POST"])
def index():
url = "ddz.imibi.cn/"
if request.method == "POST":
link = request.form.get("url")
if len(link) > 300:
url = "地址长度超过要求"
# 是不是网址判断
elif not is_http(link):
url = "请输入正确的网址"
else:
num = get_url_number() # 签发编号
num = binary_conversion(num, 10, 62) # 转换成62进制
# 随机产生url_md5字符插入位置
n = random.randint(0, 5)
url_md5 = hashlib.new('md5', bytes(link, "utf-8")).hexdigest()[-1:]
num = num[:n] + url_md5 + num[n:] # 拼接上路由的md5加密信息
num = left_str(num) # 混淆左移两位
url = num
res_obj.delete(url)
res_obj.hset(url, link, "默认")
url = "ddz.imibi.cn/" + url
context = {
"display": "block",
"url": url
}
return render_template('main.html', **context)
测试一下
修改本机hosts文件
文件路径
C:\Windows\System32\drivers\etc\hosts
结尾增加
127.0.0.1 ddz.com
启动本地服务 --host=127.0.0.1 --port=80
ddz.com通过DNS解析会映射到本机IP.
通过生成的短连接成功访问目标链接