本文讲解内容
针对两类发奖需求的四种抽奖逻辑及细节
一般H5抽奖活动的发奖需求分为
1.一定中奖(奖品库存不空的情况下)
2.不一定中奖
发奖接口的最终实现要求
1.奖品不超发
2.唯一奖品单次发放
3.对并发有一定的限制
接口实战
1.根据奖品开放时间进行抽奖
public function award($openid)
{
$award = Award::find()->where(['openid' => ''])
->andWhere(['>', 'open_at', 0])->andWhere(['<', 'open_at', time()])
->orderBy('open_at ASC')->limit(1)->one();
if (!empty($award)) {
$res = Award::updateAll(
[
'openid' => $openid
],
'code = :code AND openid = :openid',
[
':code' => $award['code'],
':openid' => ''
]
);
if ($res) {
return ArrayHelper::toArray($award);
}
}
return [];
}
这种方式,多用户并发情况下,会出现多个用户相同奖品,由于update语句限制,拿到相同奖品码的用户中只有一人能中得奖品。
2.在开放时间的基础上加上类型概率
public function randAward($openid)
{
$number = rand(0, 100);
$type = 5;
if ($number < 10) {
$type = 1;
} else if ($number < 30) {
$type = 2;
} else if ($number < 70) {
$type = 3;
} else if ($number < 80) {
$type = 4;
}
$award = Award::find()->where(['openid' => ''])
->andWhere(['>', 'open_at', 0])->andWhere(['<', 'open_at', time()])
->andWhere(['type' => $type])
->orderBy('open_at ASC')->limit(1)->one();
if (!empty($award)) {
$res = Award::updateAll(
[
'openid' => $openid
],
'code = :code AND openid = :openid',
[
':code' => $award['code'],
':openid' => ''
]
);
if ($res) {
return ArrayHelper::toArray($award);
}
}
return [];
}
这种方式,也会出现多个用户相同奖品,但加上type限制后,用户被分散在各个类型中,未中奖概率会比上面的例子低。
3.利用Redis奖品池的概念进行发奖
public function redisAward($openid)
{
try {
$redis = \Yii::$app->redis->client();
$code = $redis->LPop(self::AWARD_LIST_KEY);
} catch (Exception $err) {
return [];
}
$res = Award::updateAll(
[
'openid' => $openid
],
'code = :code AND openid = :openid',
[
':code' => $code,
':openid' => ''
]
);
if ($res) {
$award = Award::find()->where(['code' => $code])->limit(1)->one();
return ArrayHelper::toArray($award);
}
return [];
}
这种利用预先生成奖品池的方式,奖品池不空的情况下,每个用户都会取走不同奖品码,要注意的是 前期生成奖品池及后期操作奖品池时,防止奖品码复用
4.根据奖品开放时间(类型)进行抽奖,换成用sql语句进行发奖
public function sqlAward($openid)
{
$sql = "UPDATE award SET openid = :openid
WHERE open_at > 0 AND openid = '' AND open_at < :time
ORDER BY open_at ASC LIMIT 1";
$res = \Yii::$app->db->createCommand($sql, [':time' => time(), ':openid' => $openid])->execute();
if ($res) {
return Award::find()->where(['openid' => $openid])->limit(1)->asArray()->one();
}
return [];
}
一定中奖需求下,建议采用Redis奖品池或者sql语句进行update
以上四种方式在多用户并发的情况下带来不一样的结果
除了多用户并发,还会出现恶刷情况,就是同一用户并发请求
这种情况应该在真正进入抽奖逻辑之前进行限制
可以根据实际需求搭配以下方式进行限制
public function actionAward()
{
$openid = 'okjkLj7-UL4gXknY9bDkFn0O6Jos';
$redis = \Yii::$app->redis->client();
// 用户单次数
if (!$redis->sAdd(self::USER_LIST_KEY, $openid)) {
return [];
}
return $this->sqlAward($openid);
}
也可以限制抽奖人数
public function actionAward()
{
$openid = CommonTool::randString(32);
try {
$redis = \Yii::$app->redis->client();
// 抽奖用户数量
$list = $redis->sMembers(self::USER_LIST_KEY);
if (count($list) > 1000) {
return ;
}
} catch (Exception $err) {
}
$award = $this->sqlAward($openid);
}
写在最后的最后
H5活动抽奖接口需要注意几点
1.检查用户有效性
2.限制单用户访问次数
3.使用概率让用户分流,从而控制真正进入抽奖逻辑的请求
4.记录抽奖领奖等相关操作的时间设备IP等..
5.控制奖品的分布(时间,插空,概率等)
6.做好索引关系