/**
* 发放红包
* @param $money //金额
* @param $num //数量
* @param $packet //群组id确保key的唯一性
* @param $user_id //发放人
* @return array
*/
public function deliver($num,$packet,$money,$user_id){
//业务逻辑
$user = User::get($user_id);
if($user['money'] < $money){
$this->error('金额不足');
}
//获取金额
$money_arr = $this->redAlgorithm($money,$num);
if(!$money_arr){
$this->error('单个红包金额不可低于0.01');
}
//发放红包 入库操作
$key = 'packet'.$packet;
//防止对已经设置过的商品库存进行覆盖
if(!empty($this->redis->llen($key))){
$this->error('改红包已建立');
}
// 将商品存入Redis链表中
$this->redis->lPush($key,0);
foreach ($money_arr as $k=>$v){
$this->redis->lPush($key,$k+1);
//记录num个红包的金额
}
//扣除金额
//加入流水
// 延时队列,24小时没有抢则退还 86400
// todo 未测时效性(redis不行可切mysql)
// Queue::later('86390','app\job\Robexpire',['id'=>$id],'Robexpire');
$this->success('发放成功');
}
/**
* 抢红包
* @param $user_id //抢红包id
* @param $packet //群组id,获取key
* @param $boid //红包id,用来获取已分好金额
* @return array
*/
public function rob($user_id,$packet,$boid)
{
$key = 'packet'.$packet;
//已抢购用户队列
$userBuyKey = 'user_buy'.$packet;
//该用户id是否在抢购用户集合中
$userBuyStatus = $this->redis->sismember($userBuyKey,$user_id);
if($userBuyStatus){
//该用户已抢过
$this->error('您已抢过');
}
// 从链表的头部删除一个元素,返回删除的元素,因为pop操作是原子性,即使很多用户同时到达,也是依次执行
//$count为第几个
$count = $this->redis->lpop($key);
if(!$count){
//已抢完
$this->error('红包已抢完');
}
//加入已抢 将用户id添加到抢购成功用户集合中
$this->redis->sadd($userBuyKey,$user_id);
//抢成功, 操作数据库
//增加金额
//加入流水
$this->success('抢成功');
}
/**
* 获取红包金额
* @param $money //金额
* @param $count //数量
* @return array
*/
function redAlgorithm($money, $count)
{
// 参数校验
if ($count * 0.01 > $money) {
$this->error('单个红包金额不可低于0.01');
}
// 存放随机红包
$redpack = [];
// 未分配的金额
$surplus = $money;
for ($i = 1; $i <= $count; $i++) {
// 安全金额
$safeMoney = $surplus - ($count - $i) * 0.01;
// 平均金额
$avg = $i == $count ? $safeMoney : bcdiv($safeMoney, ($count - $i), 2);
// 随机红包
$rand = $avg > 0.01 ? mt_rand(1, $avg * 100) / 100 : 0.01;
// 剩余红包
$surplus = bcsub($surplus, $rand, 2);
$redpack[] = $rand;
}
// 平分剩余红包
$avg = bcdiv($surplus, $count, 2);
for ($n = 0; $n < count($redpack); $n++) {
$redpack[$n] = bcadd($redpack[$n], $avg, 2);
$surplus = bcsub($surplus, $avg, 2);
}
// 如果还有红包没有分配完时继续分配
if ($surplus > 0) {
// 随机抽取分配好的红包,将剩余金额分配进去
$keys = array_rand($redpack, $surplus * 100);
// array_rand 第二个参数为 1 时返回的是下标而不是数组
$keys = is_array($keys) ? $keys : [$keys];
foreach ($keys as $key) {
$redpack[$key] = bcadd($redpack[$key], 0.01, 2);
$surplus = bcsub($surplus, 0.01, 2);
}
}
// 红包分配结果
return $redpack;
}