需求描述
在开发优惠券系统或票务系统的时候,经常要生成纯数字码,券码要求:12位纯数字,无规律,不重复。
下面我提供一种思路,利用redis的List数据类型,Lpop+Rpush 维护一个1万个码的队列
队列数据结构
保持1万个券码数量,可以根据项目实际情况自行调整
Array
(
[0] => 478439938353
[1] => 992919492490
[2] => 476600175512
[3] => 512596230643
[4] => 627621923933
[5] => 763100176075
…………
…………
…………
[9998] => 763100176032
[9999] => 797543322210
)
用户端
用Lpop取出指定数量券码,因为Lpop具有原子性,在高并发的场景下,即使同一时间多个用户获取券码,也要按先后顺序一个个执行,这样就保证了每个用户获取出来的券码肯定不会重复的
点击查看代码
/**
* @从队列中取出指定数量的券码
* @param $num 券码数量
* @return array 返回券码数组
*/
function getCode($num){
$codeArr=[];
for($i=0;$i<$num;$i++){
$tmpCode=$redis->LPOP($key);
if(strlen($tmpCode)==10){
$codeArr[]=$tmpCode;
}
}
//如果队列中剩余券码数量不足,不足的部分要随机生成
if(count($codeArr)<$num){
$diff=$num-count($codeArr);
for($i=0;$i<$diff;$i++){
$code=rand(100000000000,999999999999);
$codeArr[]=$code;
}
}
return $codeArr;
}
定时脚本
- 定时脚本每分钟执行一次,检查队列券码数量,消耗多个券码,就补充多少个券码进去。
- 随机新生成的券码,要去数据库查下看是否已存在,如果已存在,需重新随机生成一个,再去数据库检查,直至查出有效券号为止
- Rpush批量插入队列,提高操作效率
代码示例
点击查看代码
$codeData=$redis->lRange($key,0,-1); //取出券码队列所有数据
$codeData=!is_array($codeData)?[]:$codeData;
$len=count($codeData);
$diff=10000-$len;//差多少个
$valid_arr=[];//有效核销码
if($diff>0){
getValidCode($diff);
$arr=array_merge([$key],$valid_arr);
call_user_func_array(array($redis,"rpush"),$arr);//批量插入尾部,提高操作效率
}
//递归获取有效券号
function getValidCode($need_num){
$filter_arr=[];
for($i=0;$i<10;$i++){
$code=rand(1000000000,9999999999);
if(in_array($code,$codeData)){
continue;
}
$filter_arr[]="'".$code."'";
$valid_arr[]=$code;
}
if(count($filter_arr)==0){
getValidCode($need_num);
return;
}
//查找券码是否已使用
$used_arr=[];
$filter_str=implode(",",$filter_arr);
$sql="select distinct `code` from `user_code` where `code` in ({$filter_str})";
$tmpData=DB::query($sql);
foreach($tmpData as $k=>$v){
$used_arr[]=$v['code'];
}
unset($tmpData);
$valid_arr=array_diff($valid_arr,$used_arr);//减掉数据库中已用的
if(count($valid_arr)<$need_num){
getValidCode($need_num);
}
}
项目盘点
笔者所在企业的票务系统,使用这套券码解决方案,在年数据几千万条的情况下,已经稳定运行多年