基于php实现多进制转换与兑换码生成的探索

最近要做一个兑换码生成的功能,之前有做过32位唯一码生成器,但是在业务需求中,32位的兑换码有些过长了,用户在应用内填写的时候会比较麻烦,不是很友好,倒是可以做成二维码的形式扫一下就行了,但是业务中还是存在输入兑换码的行为,所以本篇主要是关于以尽量短的字符来生成兑换码,同时要保证唯一性以及生成机制复用性(也就是利用这套机制可以生成不同种类的兑换码)的探索

以下示例代码均基于TPRCMS编写

探索一: 进制转换

生成的32位唯一码是16进制的哈希字符串,我就在想是不是可以通过提高进制来缩短字符串长度,所以有了如下的代码

代码地址: 多进制转换器ConvertLogic
其中关于10进制与62进制互转的部分,参考了《PHP 10进制与62进制互转,可用于生成短网址》

实例代码

$uuid = "cd5fd2cfeb40aafe060f4d9597348be7";
$str = ConvertLogic::convert( $uuid, 16, 62);

输出

string(32) "cd5fd2cfeb40aafe060f4d9597348be7"
string(22) "6fxdxREtzxq6qNdSghGm7t"

经过转换后发现,长度最多压缩到21~22位,感觉还是有些长

探索二: uniqid()转62进制

uniqid()可以生成一个13位以上的16进制唯一码,将它转为62进制,会得到一个更短的字符串

实验代码

$uuid = uniqid('code');
$resule = ConvertLogic::convert( $uuid, 16, 62);

//输出
//string(17) "5a5c5b182386"
//string(12) "7hoyVkRTi "

通过多次生成,从结果观察来看,有以下几个不足:

1.用这种方法生成的一批兑换码,只有后几位是变动的,生成间隔很近的话,只有最后2位不一样,前面的都一样,这样可能会造成结果比较好猜,容易被试出来。
2.唯一性不足。在批量并发多机器生成的时候,很难保证唯一性

探索三: 随机生成12位字符串,用redis保证唯一性

示例代码

//$category_uniq是类目的hash,下面这句代码主要目的是保证同一类目下不存在相同的兑换码
RedisService::redis()->switchDB()->hSetNx('code_hash_list_'.$category_uniq,  $code, $category_uniq);
//如果同类目下已存在相同的兑换码,会返回false

随机字符生成代码

/**
     * 随机字符串生成器
     * @param $length
     * @param string $strPol
     * @return null|string
     */
    public static function getRandChar($length = 15,$strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"){
        $str = null;

        $max = strlen($strPol)-1;

        for($i=0;$i<$length;$i++){
            $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
        }

        return $str;
}

通过串行100万次随机生成发现,随着数据的逐渐增多,重复的概率会逐渐上升,这就意味重新生成的次数越来越多。

探索四: 利用redis计数器"领票",用事务操作保证计数器的准确,一个兑换码领一个票

4位票号 + 12位随机码
4位票号的目的是为了保证唯一性,随机码是为了防止被轻易试出正确值。
这样即便知道了前面4位是票号,加一就行,但是后面12位可就难试了。
而且4位的票号可以支持一个类目有14538000个兑换码(62进制,"ZZZZ"-"1000"),即便是票号不够了,那票码长度+1即可
这个机制还有一个好处就是,可以根据情况自由修改兑换码长度
比如,现业务只需生成几百个码,而且对安全性没有太多要求,那就可以只需要“2位票码+4位随机符”就可以了

  • 领票代码
private static function ticket($category_uniq , $baseCount){
        $key = 'code_ticket_'.$category_uniq;
        RedisService::redis()->switchDB()->watch($key);

        $count = RedisService::redis()->switchDB()->multi()
            ->incr($key)
            ->exec();
        if($count === false){
            return self::ticket($category_uniq, $baseCount);
        }
        $ticket = $baseCount + $count;

        return $ticket;
    }

兑换码批量生成完整逻辑代码 : CodeLogic

拓展思考

  • 短网址

利用票号转62进制的思路,其实也可以做短网址
仅需要6位就可以满足500多亿的网址
而每次要新增短网址,只需要到后端领一个票码,并建立票码和真实网址的对应关系(KV)就可以了

添加新评论