我在代码片段中上传了代码 , 这是我的tp5 的抽奖 满多少人自动触发的抽奖接口, 我现在要在后台这个抽奖活动设置一个或者多个内定的手机号 , 所以同步也要修改这个接口的内定中奖人逻辑 , 帮我写出 并且不要超出中奖内定的人数哦 , 如果当前$user['mobile']是内定人之一 到时候最后一个人参与后 就可以触发优先内定人为中奖的了 如果没有设置内定 那就是随机
这是我当前的代码:
// 参与抽奖
public function start_skill($data=[],$user=[])
{
$activityId = $data['id'];
//用于测试
// $delReids = self::resetLotteryRedis($activityId);
// echo '清空活动的redis 成功';die;
$userId = $user['id']; // 获取当前用户ID
// 验证活动
$activity = Db::name('goods_activity')
->where('id', $activityId)
->where('type', 1) // 抽奖类型
->where('status', 1) // 进行中
->where('edate', '>=', time()) // 未结束
->lock(true) // 加行锁防止并发修改
->find();
if(!$activity){
return ApiReturn::r(0, [], '活动不存在或已结束');
}
// 检查活动是否已开始
if ($activity['sdate'] > time()) {
return ApiReturn::r(0, [], '活动尚未开始');
}
// Redis防并发锁
$lockKey = "lottery:lock:{$activityId}:{$userId}";
$joinKey = "lottery:joined:{$activityId}:{$userId}";
$redis = \app\common\model\Redis::handler();
$shouldSetJoinKey = false;
Db::startTrans();
try {
// 获取分布式锁
if(!$redis->set($lockKey, 1, ['nx', 'ex' => 5])){
exception('操作过于频繁,请稍后再试');
}
// 1.检查是否已参与 (使用Redis缓存提高性能)
// dump($redis->get($joinKey));die;
// $ttl = $redis->ttl($joinKey);
// 检查是否已参与时,优先查Redis,但最终以数据库为准
if ($redis->get($joinKey)) {
$hasJoined = Db::name('goods_activity_skill_record')
->where(['activity_id' => $activityId, 'user_id' => $userId])
->count();
if ($hasJoined > 0) {
exception('您已参与过该活动~');
} else {
// 修复Redis脏数据
$redis->del($joinKey);
}
}else{
$hasJoined = Db::name('goods_activity_skill_record')
->where(['activity_id' => $activityId, 'user_id' => $userId])
->count();
if ($hasJoined > 0) {
exception('您已参与过该活动!');
}
}
// Redis原子计数
$countKey = "lottery:count:{$activityId}";
$current = $redis->incr($countKey);
// dump($current);die;
// 首次设置过期时间
if ($current == 1) {
$redis->expire($countKey, $activity['edate'] - time()); // 设置到活动结束
}
// 获取奖品列表
$draw_goods = Db::name('goods_activity_details')->alias('d')
->join('goods g','g.id = d.goods_id')
->where('d.activity_id', $activityId)
->field('d.goods_id,d.join_number,g.name as goods_name,g.thumb as goods_thumb')
->find();
if(!$draw_goods){
exception('活动奖品不存在');
}
// 检查人数限制
Jlog('检查人数限制',$current.'====='.$draw_goods['join_number'],'skill_draw');
if($current > $draw_goods['join_number']){
$redis->decr($countKey); // 回滚计数
exception('活动人数已满,已自动开奖');
}
// 记录参与信息
$recordId = Db::name('goods_activity_skill_record')->insertGetId([
'activity_id' => $activityId,
'user_id' => $userId,
'create_time' => time(),
'is_winner' => 0,
// 'verify_code' => ''
'ip' => request()->ip() // 记录用户IP
]);
if (!$recordId) {
$redis->decr($countKey); // 回滚计数
exception('参与失败,请重试');
}
// 修改这里:只有当数据库插入成功时才设置Redis标记
if ($recordId) {
$shouldSetJoinKey = true;
}
// 人数达标触发开奖
if($current == $draw_goods['join_number']){
$drawResult = self::syncDraw($activityId,$userId,$draw_goods['join_number'],$activity['icon']);//立即执行开奖 非队列开奖
Jlog('事务提交20', '成功'.$drawResult, 'skill_draw');
if ($drawResult['code'] == 1) {
$is_draw_user = 1;//中奖者提示
Db::commit();
Jlog('事务提交21', '成功'.$recordId, 'skill_draw');
}else{
exception($drawResult['msg']);
}
}else{
$is_draw_user = 0;//非中奖者提示
Db::commit();
Jlog('事务提交1', '成功'.$recordId, 'skill_draw');
}
} catch (\Exception $e) {
// 确保不设置Redis标记
$shouldSetJoinKey = false;
// 记录错误日志
// Log::error("抽奖参与失败: ".$e->getMessage());
Jlog('抽奖参与失败',$e->getMessage(),'skill_draw');
Db::rollback();
return ApiReturn::r(0, [], $e->getMessage());
} finally {
$redis->del($lockKey); // 释放锁
}
// 只有事务成功提交后才设置Redis标记 缓存参与状态
if ($shouldSetJoinKey) {
Jlog('抽奖参与走完了',$joinKey,'skill_draw');
$redis->setex($joinKey, 3600, 1);
}
if($is_draw_user == 1){
return ApiReturn::r(1, ['is_draw'=>1],'参与成功,活动即将自动开奖');
}else{
return ApiReturn::r(1, ['is_draw'=>0],'参与成功');
}
}
// 触发自动开奖
/**
* 同步开奖方法
* @param int $activityId 活动ID
* @return array|bool 中奖用户ID数组或false
*/
private function syncDraw($activityId, $userId, $joinNumber, $activity_thumb) {
$redis = \app\common\model\Redis::handler();
$lockKey = "lottery:drawing:{$activityId}";
// 获取分布式锁(防止并发开奖)
if (!$redis->set($lockKey, 1, ['nx', 'ex' => 30])) {
return ['code' => 0, 'msg' => '网络拥堵,请稍后重试'];
}
Db::startTrans();
try {
// 1. 获取活动信息(加行锁)
$activity = Db::name('goods_activity')
->where('id', $activityId)
->lock(true)
->find();
if (!$activity || $activity['status'] != 1) {
throw new \Exception('活动信息错误');
}
// 2. 抽奖逻辑 目前是随机 最后一个人抽奖就开奖 , 中奖是谁 应该自定义 后续测试的时候
$winnerId = Db::name('goods_activity_skill_record')
->where('activity_id', $activityId)
->where('is_winner', 0)
->orderRaw('RAND()')
->value('user_id');
if (empty($winnerId)) {
throw new \Exception('暂无有效参与者');
}
// 3. 更新中奖记录和活动状态
$code = rand(1000, 9999);
Db::name('goods_activity_skill_record')
->where('activity_id', $activityId)
->where('user_id', $winnerId)
->update([
'is_winner' => 1,
'verify_code' => $code,
'win_time' => time()
]);
Db::name('goods_activity')
->where('id', $activityId)
->update([
'draw_status' => 1,//0进行中 1人满自动开奖
'draw_user_id' => $userId,//触发活动开奖的用户id
'draw_time' => time()
]);
Jlog('开奖通知参数',[$winnerId, $activityId,$activity_thumb],'skill_draw');
// 5. 异步通知 怎么通知给大哥了???
$sendres = self::asyncNotifyWinners($winnerId, $activityId,$activity_thumb);
if(!$sendres){
exception('通知中奖用户error');
}
Db::commit();
return ['code' => 1, 'msg' => '已成功开奖'];
} catch (\Exception $e) {
Db::rollback();
Jlog('开奖失败', $e->getMessage(), 'skill_draw');
return ['code' => 0, 'msg' => $e->getMessage()];
} finally {
$redis->del($lockKey); // 确保锁释放
}
}
/**
* 异步通知中奖者(模拟异步)
*/
private function asyncNotifyWinners($winnerId, $activityId,$activity_thumb)
{
//通知中奖者 目前没通知别人
$r = SystemMessage::send_msg(
$winnerId,
lang('活动消息'),
'您参与的抽奖活动已开奖,请前往[抽奖专区-我参与的]查看',
1,
3,
1,
$activity_thumb,
'/pages/activity/seckill/seckill-detail/index?id='.$activityId
);
return $r;
}