PHP 生成唯一编码字符的方法

经常有项目需要使用一些邀请码、激活码、兑换码等唯一的编码!通常的做法是随机生成一个编码,然后进行数据库查询匹配,如果已经存在则再随机生成一个,如是循环!但在编码位数有限(比如:8位的编码)和已有大量编码数据的情况下,上述做法将逐步造成编码唯一率生成概率低下,最终因循环查库导致程序超时或失败!

那么有没有一种编码的生成方式不需要进行数据库查询匹配,在其生成的时候就是唯一编码呢?当然有,而且算法很多,但是向普遍的方法(比如利用时间戳、MD5等)生成的编码位数都会比较长,如果你不在意编码长那么他们将是比较好的方法,但是如果你要求的编码是比较短的,那么就需要另寻他法!

通常我们建立数据库都会有一个自增字段(AUTO_INCREMENT),今天我们就用这个自增字段的值来构建我们所需的编码。在你继续研读这篇文章前,你需要先了解其优缺点:
[优点] 因为使用自增字段的值通过算法构建编码,所以写入数据库时不需要进行数据存在检查;
[优点] 通过编码可以反算出其对应的自增字段值,那么数据库因为索引的关系速度将非常可观;
[缺点] 编码含有数字,字母
[缺点] 编码位数必须是 8 位以上
[缺点] 自增字段的类型需要是 UNSIGNED INT

1. 进制说明
十进制、十六进制对于大家来说比较常见,十进制使用 0-9 来表示,而十六进制则使用 0-9、A-F 来表示;
 那么使用 0-9、A-Z、a-z 来表示的是什么进制呢:10个数据字,26个小写字母、26个大写字母,那就是六十二进制了;
 那么同理,四十二进制需要使用 0-9、A-Z、a-f 来表示;

使用越大的进制,同样的数值使用的字符数将越少,比如:
16 = F(十进制/十六进制)、 4294967295 = 4gfFC3(十进制/六十二进制)
可以看到 4294967295(10个字符) 数值当使用六十二进制来表示的时候是 4gfFC3(仅需6个字符);

2. 构建进制码
对于 MySQL 数据库中 UNSIGNED INT 的自增字段值,其值范围在 1 – 4294967295
当程序从数据库里读取一条记录后,我们将使用四十二进制对自增字段值进行进制转化,其值范围将在 1 – WaB6U3

为什么要使用四十二进制?
看下十进制 4294967295 对应的四十进制/四十二进制/六十四进制值:11bSYMF/WaB6U3/4gfFC3
可以发现当使用四十进制时需要7个字符,而且两个只需要6个字符
四十二进制只是 UNSIGNED INT 的最大值换算所需字符最少的最小进制;

      function toBinary( $num, $binary ){

          $length = strlen($binary);   

          while( $num > $length – 1 ){ $out = $binary[fmod($num,$length)].$out; $num = floor($num/$length); }     

          return $binary[$num].$out;   

      }

      function toBase( $str, $binary ){

          $size = strlen($str) – 1; $length = strlen($binary);

          $str = str_split($str); $out = strpos($binary, array_pop($str));   

          foreach( $str as $i=>$char ) $out += strpos($binary,$char)*pow($length, $size-$i);

          return $out;   

      }

      $binary = ‘0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef’;

      echo toBinary(4294967295,$binary),’ = ‘,toBase(‘WaB6U3’,$binary);

3. 构建编码

进制码构建算法确认完后,那么这里我们将引入一个验证码,数据库中编码表字段如下:自增字段、验证码字段
编码 = 截取8位(自增字段的进制码 [尾部拼接] 验证码)

 验证码是写入数据库生成自增字段时就写入的
验证码是一串随机的字符串(不唯一),其字符个数和编码的个数一致,其字符的内容必须是 g-z;
就像十六进制一样,F 字母以上的其他字母是不需要的,而 g-z 是四十二进制不需要的。

4. 验证编码
    对于邀请码、激活码、兑换码等之类的编码,总会有人过来对编码进行验证,下面就介绍如何验证
    还记得之前介绍优缺点的时候说过:编码可以反算出其对应的自增字段值
    当客户输入一个编码时:      先过滤并保存编码中 g-z 的字符(过滤后可以提取到进制码)
      然后将进制码转为十进制的数,查询数据库获取编码记录
      最后根据编码记录进行构建编码,与用户输入的编码进行覈对。

编码强化
  1. 由于使用了有序的数字和字母进行进制转化,所以根据编码后可以很容易猜到自增字段值以及验证码的内容字母
    强化:使用无序且跨用数字和字母进行进制转化和反算
    // 验证码内容字母:68hIjklMnoPqrstTwxUz
    $binary = ‘W0G5S9ACDyEHiJKLmNOd3YR1u7VpQXZab4c2Befgv’;

  2. 当编码为8位,进制码6位时
    按照 “编码=截取8位(自增字段的进制码 [尾部拼接] 验证码)” 构建的编码被暴力使用400次即可破译
    强化:按照 “编码=截取8位(自增字段的进制码 [有序穿插] 验证码)” 构建的编码被暴力使用11200次即可破译

    当编码为8位,进制码1位时
    按照 “编码=截取8位(自增字段的进制码 [尾部拼接] 验证码)” 构建的编码被暴力使用1280000000次即可破译
    强化:按照 “编码=截取8位(自增字段的进制码 [有序穿插] 验证码)” 构建的编码被暴力使用8960000000次即可破译

    当编码为8位,进制码5位时
    按照 “编码=截取8位(自增字段的进制码 [尾部拼接] 验证码)” 构建的编码被暴力使用8000次即可破译
    强化:按照 “编码=截取8位(自增字段的进制码 [有序穿插] 验证码)” 构建的编码被暴力使用228000次即可破译

数据参考

 

自增字段
1
130691231
4294967295

验证码[g-z]
ghijklmn
hijklmno
ijklmnop

->
->
->
->

进制码
1
fffff
WaB6U3

拼接编码
1ghijklm
fffffhij
WaB6U3ij

穿插编码
g1hijklm
hifffffj
WiaB6Uj3

 

点赞