< 返回版块

importcjj 发表于 2019-12-26 14:21

Tags:rust

人们常用数据库的自增ID作为web资源的ID,形如/articles/1230098/videos/9527这样的。虽然这种做法很简单,但是也增加了被全量爬取数据的风险。爬虫制作者只要指定一个起始ID,然后不停的自增可以请求全部数据。

如果你上过油管并仔细观察过油管视频页面的地址,就会发现油管的视频ID是由字母数字和下划线等组成的。如果前端通过字母ID请求资源,后端拿到字母ID后通过某种规则将其解码为数字ID,然后再通过该数字ID去数据库读取数据。这种做法在一定程度上可以防止直接使用自增ID带来的问题。

笔者出于好奇,也在网上搜索了相关问题。一篇博文给出了一种可行的实现方式,看起来效果不错。这篇文章已经是5年前的了,博主使用php实现了初版,然后各路网友给出了由不同语言实现的版本。作为一个RUST爱好者,发现居然没有RUST版,当然不能忍,当即就想动手来做。代码也没多少,没花多少时间就写完了。不过由于是依样画葫芦,没能完全理解其中的原理,笔者也是很担忧这东西能不能在实际项目中使用,毕竟如果生成的ID有重复,又或是解码结果和原数字ID不一致的话就凉凉了。所以写完就那么放着了。

突然有一天,笔者感觉自己灵光一闪,突然就领悟了其中的数学原理。这尼玛不就是一个10进制和N进制的换算问题么。比如十六进制就是用16个数字和字母组成的字符集来表示数字,那么这篇博文说的就是用N个字符来表示数字啊!但是我看博主的实现代码完全没看出来,我真是太菜了!

可能有些朋友对这个N进制还是有点疑惑,我这里再稍微说下。假如我们要用三个字母xyz来表示数字。如果用一位字母,则可以表示0-2。如果是两位就是0-8,例如XX表示0,XY表示1(如果对这里不能理解,可以去看下2进制和16进制)。M位就是0到3的M次方减一。

好了我们来说下具体实现,假如我们要用abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_来表示生成ID,用一位字母就可以表示0-63这些ID,二位可以表示64的2次方,3位就是64的3次方。随着位数的增加,可以表示的数字也越来越大。理论上,如果对生成字母长度不设上限,可以表示无穷大。我们的RUST里的u128最大值是 340282366920938463463374607431768211455

那么如果要表示这个数字,我们的字符ID需要22位。64的22次方已经超过了这个范围。

在理解了这个原理之后,笔者把之前依样画葫芦写下的代码根据自己的思路重构了一番,解决了该博主实现中的一些bug吧,同时也处理了一些可能的overflow奔溃。项目叫做alphaid,放在GitHub了。笔者还写了详细的文档和较多的测试用例,发布到了crates.io

这里放一个简单的例子:

use alphaid::AlphaId;

let alphaid = AlphaId::new();
assert_eq!(alphaid.encode(1350997667), b"90F7qb");
assert_eq!(alphaid.decode(b"90F7qb"), Ok(1350997667));

你也可以采用自己的字符集,最好将字母打乱顺序,这样的话写爬虫的人也猜不出规则了:

let alphaid = AlphaId::builder().pad(2)
    .chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ".as_bytes().to_vec())
    .build();
assert_eq!(alphaid.encode(0), b"AB");
assert_eq!(alphaid.decode(b"AB"), Ok(0));

友情提示,最好把c, s, f, h, u, i, t这些字母拿掉,否则产生了fuck等这些不好的词汇就不好咯!!

注意,这种方式只是一种思路,不代表youtube就采用了这种方式。

最后,如果本文章或者本项目对你有帮助的话,欢迎点赞!有什么反馈也欢迎提出。

附上项目地址 github

评论区

写评论
作者 importcjj 2019-12-27 11:25

Base64是一种基于64个可打印字符来表示二进制数据的表示方法。 这个是来表示十进制,且不限定64个字符 对以下内容的回复:

nintha 2019-12-27 10:52

这个和Base64有区别嘛

1 共 2 条评论, 1 页