lottery_type == LotteryEnum::LOTTERY_TYPE_TIME) { $now = time(); if ($activity->lottery_time && $now >= $activity->lottery_time) { throw new BusinessException('开奖时间已过,无法参与', ErrorCodeEnum::LOTTERY_NOT_IN_TIME); } } // 3. 验证参与人数限制(仅对按人数开奖有效) if ($activity->lottery_type == LotteryEnum::LOTTERY_TYPE_PEOPLE) { $drawStats = LotteryRecordService::getActivityDrawStats($activityId); if ($drawStats['total_draw'] >= $activity->lottery_people_num) { throw new BusinessException('参与人数已满,无法参与', ErrorCodeEnum::LOTTERY_REACH_LIMIT); } } // 4. 验证用户资格 if (!static::validateUserQualification($activity, $userId)) { throw new BusinessException('用户不符合参与条件', ErrorCodeEnum::LOTTERY_USER_NOT_QUALIFIED); } // 5. 检查用户抽奖机会 $userChance = LotteryChanceService::getUserChance($activityId, $userId); if (!$userChance || !LotteryChanceService::hasChance($userChance)) { throw new BusinessException('没有抽奖机会', ErrorCodeEnum::LOTTERY_NO_CHANCE); } // 6. 检查用户参与次数限制 if (!static::checkUserDrawLimit($activity, $userId)) { throw new BusinessException('已达到参与次数上限', ErrorCodeEnum::LOTTERY_REACH_LIMIT); } // 7. 防重复抽奖检查(基于订单) if ($triggerOrderId && static::hasDrawnForOrder($activityId, $userId, $triggerOrderId)) { throw new BusinessException('该订单已参与过抽奖', ErrorCodeEnum::LOTTERY_ORDER_ALREADY_DRAWN); } // TODO: 8. 使用分布式锁防止并发(暂时移除,后续实现) // $lockKey = "lottery_lock_{$activityId}_{$userId}"; // $lockAcquired = static::acquireLock($lockKey, 10); // if (!$lockAcquired) { // throw new Exception('操作太频繁,请稍后再试'); // } // try { // 8. 根据开奖方式处理抽奖流程 return static::handleLotteryByType($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount); // } finally { // // 释放锁 // static::releaseLock($lockKey); // } } /** * 根据开奖方式处理抽奖 */ private static function handleLotteryByType($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount) { // 第一步:统一创建参与记录 $drawRecord = static::createParticipationRecord($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount); switch ($activity->lottery_type) { case LotteryEnum::LOTTERY_TYPE_INSTANT: // 即抽即中:立即执行抽奖并更新记录 return static::executeInstantDraw($drawRecord, $activity); case LotteryEnum::LOTTERY_TYPE_TIME: // 按时间开奖:记录参与,等待定时任务开奖 return static::handleTimeLottery($drawRecord, $activity); case LotteryEnum::LOTTERY_TYPE_PEOPLE: // 按人数开奖:记录参与,检查是否达到开奖人数 return static::handlePeopleLottery($drawRecord, $activity); default: throw new BusinessException('不支持的开奖方式', ErrorCodeEnum::LOTTERY_ACTIVITY_NOT_FOUND); } } /** * 创建参与记录(统一入口) */ private static function createParticipationRecord($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount) { // 开启事务 Db::startTrans(); try { // 1. 消耗用户抽奖机会 $userChance = LotteryChanceService::getUserChance($activity->id, $userId); if (!LotteryChanceService::useChance($userChance)) { throw new BusinessException('抽奖机会使用失败'); } // 2. 创建参与记录(状态为已参与) $drawRecord = new LotteryDrawRecord([ 'activity_id' => $activity->id, 'user_id' => $userId, 'prize_id' => 0, // 暂时没有奖品ID 'status' => LotteryEnum::DRAW_STATUS_PARTICIPATED, // 状态:已参与 'is_win' => 0, // 未开奖 'trigger_type' => $triggerType, 'trigger_order_id' => $triggerOrderId, 'trigger_amount' => $triggerAmount, 'win_info' => json_encode([]), // 暂时没有中奖信息 'draw_ip' => request()->ip(), 'draw_time' => time(), 'device_info' => request()->header('user-agent', ''), ]); if (!$drawRecord->save()) { throw new Exception('创建抽奖记录失败'); } // 提交事务 Db::commit(); return $drawRecord; } catch (Exception $e) { Db::rollback(); throw $e; } } /** * 执行即抽即中的抽奖逻辑 */ private static function executeInstantDraw($drawRecord, $activity) { try { // 1. 获取可用奖品 $prizes = static::getAvailablePrizes($activity); if (empty($prizes)) { throw new BusinessException('暂无可抽取的奖品'); } // 2. 执行抽奖算法 $selectedPrize = static::executeLotteryAlgorithm($prizes); // 3. 减少库存 if (!static::decreasePrizeStock($selectedPrize)) { throw new BusinessException('奖品库存不足'); } // 4. 更新抽奖记录 $isWin = $selectedPrize->type != LotteryEnum::PRIZE_TYPE_NO_PRIZE; $winInfo = $isWin ? static::buildWinInfo($selectedPrize) : []; $drawRecord->prize_id = $selectedPrize->id; $drawRecord->status = $isWin ? LotteryEnum::DRAW_STATUS_WIN : LotteryEnum::DRAW_STATUS_NO_WIN; $drawRecord->is_win = $isWin ? 1 : 0; $drawRecord->win_info = json_encode($winInfo); $drawRecord->save(); // 5. 如果中奖,创建中奖记录 $winRecord = null; if ($isWin) { $winRecord = new \app\common\model\lottery\LotteryWinRecord([ 'draw_record_id' => $drawRecord->id, 'activity_id' => $activity->id, 'user_id' => $drawRecord->user_id, 'prize_id' => $selectedPrize->id, 'prize_name' => $selectedPrize->name, 'prize_type' => $selectedPrize->type, 'prize_value' => json_encode(static::buildPrizeValue($selectedPrize)), 'deliver_status' => LotteryEnum::DELIVER_STATUS_PENDING, ]); if (!$winRecord->save()) { throw new Exception('创建中奖记录失败'); } // 自动发放奖品 if ($selectedPrize->deliver_type == LotteryEnum::DELIVER_TYPE_AUTO) { static::autoDeliverPrize($winRecord, $selectedPrize); } } // 6. 更新活动统计 static::updateActivityStats($activity, $isWin); // 7. 返回抽奖结果 $result = [ 'draw_id' => $drawRecord->id, 'is_win' => $drawRecord->is_win, //"status" => $drawRecord->status, 'lottery_type' => $activity->lottery_type, 'lottery_time' => $activity->lottery_time ?? 0, 'prize' => [ 'id' => $selectedPrize->id, 'name' => $selectedPrize->name, 'type' => $selectedPrize->type, 'type_text' => $selectedPrize->type_text, 'image' => cdnurl($selectedPrize->image), 'win_prompt' => $selectedPrize->win_prompt ], ]; if ($winRecord) { $result['win_record_id'] = $winRecord->id; $result['deliver_status'] = $winRecord->deliver_status; // 只有兑换码类型的奖品才记录兑换码信息 if ($selectedPrize->type == LotteryEnum::PRIZE_TYPE_CODE) { $result['exchange_code'] = $winRecord->exchange_code; } } return $result; } catch (Exception $e) { // 如果发生错误,将状态改为未中奖 $drawRecord->status = LotteryEnum::DRAW_STATUS_NO_WIN; $drawRecord->is_win = 0; $drawRecord->save(); throw new BusinessException('抽奖失败'); } } /** * 处理定时抽奖 */ private static function handleTimeLottery($drawRecord, $activity) { // 验证开奖时间 if (isset($activity->lottery_time) && $activity->lottery_time < time()) { throw new BusinessException('活动开奖时间已过,无法参与'); } return [ 'draw_id' => $drawRecord->id, 'is_win' => 0, 'status' => LotteryEnum::DRAW_STATUS_PARTICIPATED, // 等待开奖 'lottery_type' => $activity->lottery_type, 'lottery_time' => $activity->lottery_time ?? 0, 'message' => isset($activity->lottery_time) ? '参与成功,等待' . date('Y-m-d H:i:s', $activity->lottery_time) . '开奖' : '参与成功,等待开奖' ]; } /** * 处理按人数抽奖 */ private static function handlePeopleLottery($drawRecord, $activity) { // 检查是否达到开奖人数 $participantCount = LotteryDrawRecord::where('activity_id', $activity->id) ->where('status', LotteryEnum::DRAW_STATUS_PARTICIPATED) ->count(); $result = [ 'draw_id' => $drawRecord->id, 'is_win' => 0, 'status' => 'waiting', // 等待开奖 'lottery_type' => $activity->lottery_type, 'current_participants' => $participantCount, 'required_participants' => $activity->lottery_people_num ?? 100 ]; if ($participantCount >= ($activity->lottery_people_num ?? 100)) { // 达到人数,触发开奖(这里可以异步处理) // TODO: 触发按人数开奖的处理逻辑 $result['message'] = '参与成功,已达到开奖人数,正在开奖中...'; } else { $remainingCount = ($activity->lottery_people_num ?? 100) - $participantCount; $result['message'] = "参与成功,还需要{$remainingCount}人参与即可开奖"; } return $result; } /** * 处理抽奖核心逻辑 */ private static function processDrawLottery($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount) { // 开启事务 Db::startTrans(); try { // 1. 获取可用奖品 $prizes = static::getAvailablePrizes($activity); if (empty($prizes)) { throw new Exception('暂无可抽取的奖品'); } // 2. 执行抽奖算法 $selectedPrize = static::executeLotteryAlgorithm($prizes); // 3. 减少库存 if (!static::decreasePrizeStock($selectedPrize)) { throw new Exception('奖品库存不足'); } // 4. 消耗用户抽奖机会 $userChance = LotteryChanceService::getUserChance($activity->id, $userId); if (!LotteryChanceService::useChance($userChance)) { throw new Exception('抽奖机会使用失败'); } // 5. 创建抽奖记录 $isWin = $selectedPrize->type != LotteryEnum::PRIZE_TYPE_NO_PRIZE; $winInfo = $isWin ? static::buildWinInfo($selectedPrize) : []; $drawRecord = LotteryRecordService::createDrawRecord( $activity->id, $userId, $selectedPrize->id, $isWin, $triggerType, $triggerOrderId, $triggerAmount, $winInfo ); // 6. 如果中奖,创建中奖记录 $winRecord = null; if ($isWin) { $winRecord = LotteryRecordService::createWinRecord( $drawRecord->id, $activity->id, $userId, $selectedPrize->id, $selectedPrize->name, $selectedPrize->type, static::buildPrizeValue($selectedPrize) ); // 自动发放奖品 if ($selectedPrize->deliver_type == LotteryEnum::DELIVER_TYPE_AUTO) { LotteryRecordService::autoDeliverPrize($winRecord, $selectedPrize); } } // 7. 更新活动统计 static::updateActivityStats($activity, $isWin); // 提交事务 Db::commit(); // 8. 返回抽奖结果 return static::buildDrawResult($drawRecord, $selectedPrize, $winRecord); } catch (Exception $e) { Db::rollback(); throw $e; } } /** * 记录参与(用于按时间开奖和按人数开奖) */ private static function recordParticipation($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount) { // 开启事务 Db::startTrans(); try { // 1. 消耗用户抽奖机会 $userChance = LotteryChanceService::getUserChance($activity->id, $userId); if (!LotteryChanceService::useChance($userChance)) { throw new Exception('抽奖机会使用失败'); } // 2. 创建参与记录(未开奖状态) $drawRecord = LotteryRecordService::createDrawRecord( $activity->id, $userId, 0, // 暂时没有奖品ID 0, // 未开奖 $triggerType, $triggerOrderId, $triggerAmount, [] // 暂时没有中奖信息 ); // 提交事务 Db::commit(); // 3. 返回参与结果 return [ 'draw_id' => $drawRecord->id, 'is_win' => 0, 'status' => 'waiting', // 等待开奖 'lottery_type' => $activity->lottery_type, 'lottery_time' => $activity->lottery_time ?? 0, 'message' => $activity->lottery_type == LotteryEnum::LOTTERY_TYPE_TIME ? '参与成功,等待' . date('Y-m-d H:i:s', $activity->lottery_time) . '开奖' : '参与成功,等待开奖' ]; } catch (Exception $e) { Db::rollback(); throw new BusinessException('创建参与记录失败'); } } /** * 定时任务:处理定时开奖活动 * 用于定时任务调用,批量处理到时间开奖的活动 */ public static function processScheduledLotteries() { // 获取所有到达开奖时间的活动 $activities = LotteryActivity::where('lottery_type', LotteryEnum::LOTTERY_TYPE_TIME) ->where('status', LotteryEnum::STATUS_ONGOING) ->where('lottery_time', '<=', time()) ->select(); $processedCount = 0; foreach ($activities as $activity) { try { $count = static::executeScheduledDraw($activity); $processedCount += $count; // 更新活动状态为已结束 $activity->status = LotteryEnum::STATUS_ENDED; $activity->save(); } catch (Exception $e) { trace("定时开奖失败 - 活动ID: {$activity->id}, 错误: " . $e->getMessage(), 'error'); } } return $processedCount; } /** * 执行定时开奖 */ private static function executeScheduledDraw($activity) { // 获取所有参与记录 $participantRecords = LotteryDrawRecord::where('activity_id', $activity->id) ->where('status', LotteryEnum::DRAW_STATUS_PARTICIPATED) ->select(); if (empty($participantRecords)) { return 0; } $processedCount = 0; foreach ($participantRecords as $drawRecord) { try { // 为每个参与记录执行抽奖 static::executeDrawForRecord($drawRecord, $activity); $processedCount++; } catch (Exception $e) { trace("用户开奖失败 - 记录ID: {$drawRecord->id}, 错误: " . $e->getMessage(), 'error'); // 设置为未中奖 $drawRecord->status = LotteryEnum::DRAW_STATUS_NO_WIN; $drawRecord->is_win = 0; $drawRecord->save(); } } return $processedCount; } /** * 为单个抽奖记录执行开奖 */ private static function executeDrawForRecord($drawRecord, $activity) { // 获取可用奖品 $prizes = static::getAvailablePrizes($activity); if (empty($prizes)) { // 没有奖品,设置为未中奖 $drawRecord->status = LotteryEnum::DRAW_STATUS_NO_WIN; $drawRecord->is_win = 0; $drawRecord->save(); return; } // 执行抽奖算法 $selectedPrize = static::executeLotteryAlgorithm($prizes); // 减少库存 if (!static::decreasePrizeStock($selectedPrize)) { // 库存不足,设置为未中奖 $drawRecord->status = LotteryEnum::DRAW_STATUS_NO_WIN; $drawRecord->is_win = 0; $drawRecord->save(); return; } // 更新抽奖记录 $isWin = $selectedPrize->type != LotteryEnum::PRIZE_TYPE_NO_PRIZE; $winInfo = $isWin ? static::buildWinInfo($selectedPrize) : []; $drawRecord->prize_id = $selectedPrize->id; $drawRecord->status = $isWin ? LotteryEnum::DRAW_STATUS_WIN : LotteryEnum::DRAW_STATUS_NO_WIN; $drawRecord->is_win = $isWin ? 1 : 0; $drawRecord->win_info = json_encode($winInfo); $drawRecord->save(); // 如果中奖,创建中奖记录 if ($isWin) { $winRecord = new \app\common\model\lottery\LotteryWinRecord([ 'draw_record_id' => $drawRecord->id, 'activity_id' => $activity->id, 'user_id' => $drawRecord->user_id, 'prize_id' => $selectedPrize->id, 'prize_name' => $selectedPrize->name, 'prize_type' => $selectedPrize->type, 'prize_value' => json_encode(static::buildPrizeValue($selectedPrize)), 'deliver_status' => LotteryEnum::DELIVER_STATUS_PENDING, ]); $winRecord->save(); // 自动发放奖品 if ($selectedPrize->deliver_type == LotteryEnum::DELIVER_TYPE_AUTO) { static::autoDeliverPrize($winRecord, $selectedPrize); } } // 更新活动统计 static::updateActivityStats($activity, $isWin); } /** * 处理按人数开奖 */ private static function handlePeopleBasedLottery($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount) { // 先记录参与 $result = static::recordParticipation($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount); // 检查是否达到开奖人数 $drawStats = LotteryRecordService::getActivityDrawStats($activity->id); $participantCount = $drawStats['total_draw']; if ($participantCount >= $activity->lottery_people_num) { // 达到人数,触发开奖(这里可以异步处理) // TODO: 触发按人数开奖的处理逻辑 $result['message'] = '参与成功,已达到开奖人数,正在开奖中...'; } else { $remainingCount = $activity->lottery_people_num - $participantCount; $result['message'] = "参与成功,还需要{$remainingCount}人参与即可开奖"; } return $result; } /** * 获取可用奖品列表 */ private static function getAvailablePrizes($activity) { $prizes = static::getValidPrizes($activity->id); // 如果开启按人数解锁功能 if ($activity->unlock_by_people) { $currentPeopleCount = LotteryChanceService::getActivityParticipants($activity->id); $prizes = $prizes->filter(function($prize) use ($currentPeopleCount) { return static::isPrizeUnlocked($prize, $currentPeopleCount); }); } return $prizes; } /** * 执行抽奖算法(基于概率权重) */ private static function executeLotteryAlgorithm($prizes) { // 检查是否有奖品 if (empty($prizes)) { return null; } // 将奖品转换为数组(如果是集合对象) $prizeArray = is_array($prizes) ? $prizes : $prizes->toArray(); // 使用BC函数计算总概率 $totalProbability = '0'; foreach ($prizeArray as $prize) { $totalProbability = bcadd($totalProbability, $prize['probability'] ?? '0', 4); } // 如果总概率为0,返回未中奖奖品 if (bccomp($totalProbability, '0', 4) == 0) { return static::findNoPrizePrize($prizeArray); } // 生成随机数(0-总概率之间) $randomNumber = bcdiv(mt_rand(0, bcmul($totalProbability, '10000', 4)), '10000', 4); // 按概率选择奖品 $currentProbability = '0'; foreach ($prizeArray as $prize) { $currentProbability = bcadd($currentProbability, $prize['probability'] ?? '0', 4); if (bccomp($randomNumber, $currentProbability, 4) <= 0) { return $prize; } } // 保底返回未中奖类型的奖品 return static::findNoPrizePrize($prizeArray); } /** * 查找未中奖类型的奖品 */ private static function findNoPrizePrize($prizeArray) { // 优先查找未中奖类型的奖品 foreach ($prizeArray as $prize) { if (($prize['type'] ?? 0) == LotteryEnum::PRIZE_TYPE_NO_PRIZE) { return $prize; } } // 如果没找到未中奖类型,返回第一个奖品作为兜底 return isset($prizeArray[0]) ? $prizeArray[0] : null; } /** * 验证用户资格 */ private static function validateUserQualification($activity, $userId) { // 检查用户群体限制 switch ($activity->user_limit_type) { case LotteryEnum::USER_LIMIT_ALL: return true; case LotteryEnum::USER_LIMIT_LEVEL: return static::checkUserLevel($userId, $activity->user_limit_value); case LotteryEnum::USER_LIMIT_TAG: return static::checkUserTag($userId, $activity->user_limit_value); default: return false; } } /** * 检查用户等级 */ private static function checkUserLevel($userId, $limitValue) { if (empty($limitValue)) { return true; } $user = User::find($userId); return $user && in_array($user->level, (array)$limitValue); } /** * 检查用户标签 */ private static function checkUserTag($userId, $limitValue) { if (empty($limitValue)) { return true; } // 这里需要根据实际的用户标签系统实现 // 暂时返回true return true; } /** * 检查用户抽奖次数限制 */ private static function checkUserDrawLimit($activity, $userId) { if (!$activity->person_limit_num) { return true; } $drawCount = LotteryRecordService::getUserDrawCount($activity->id, $userId); return $drawCount < $activity->person_limit_num; } /** * 检查订单是否已抽奖 */ private static function hasDrawnForOrder($activityId, $userId, $orderId) { return LotteryRecordService::hasUserDrawnForOrder($activityId, $userId, $orderId); } /** * 构建中奖信息 */ private static function buildWinInfo($prize) { return [ 'prize_id' => $prize->id, 'prize_name' => $prize->name, 'prize_type' => $prize->type, 'prize_image' => $prize->image, 'win_prompt' => $prize->win_prompt ]; } /** * 构建奖品价值信息 */ private static function buildPrizeValue($prize) { $prizeValue = [ 'type' => $prize->type, 'name' => $prize->name, 'image' => $prize->image ]; switch ($prize->type) { case LotteryEnum::PRIZE_TYPE_COUPON: $prizeValue['coupon_id'] = $prize->coupon_id; break; case LotteryEnum::PRIZE_TYPE_REDPACK: $prizeValue['amount'] = $prize->amount; break; case LotteryEnum::PRIZE_TYPE_SHOP_GOODS: $prizeValue['goods_id'] = $prize->goods_id; $prizeValue['goods_sku_id'] = $prize->goods_sku_id; break; case LotteryEnum::PRIZE_TYPE_CODE: $prizeValue['exchange_code'] = static::getAvailableExchangeCode($prize); break; } return $prizeValue; } /** * 自动发放奖品 */ private static function autoDeliverPrize($winRecord, $prize) { try { switch ($prize->type) { case LotteryEnum::PRIZE_TYPE_COUPON: static::deliverCoupon($winRecord, $prize); break; case LotteryEnum::PRIZE_TYPE_REDPACK: static::deliverRedPacket($winRecord, $prize); break; case LotteryEnum::PRIZE_TYPE_CODE: static::deliverExchangeCode($winRecord, $prize); break; } } catch (Exception $e) { LotteryRecordService::markWinRecordAsFailed($winRecord, $e->getMessage()); } } /** * 发放优惠券 */ private static function deliverCoupon($winRecord, $prize) { // 这里调用优惠券发放接口 // 示例代码,需要根据实际优惠券系统实现 LotteryRecordService::markWinRecordAsDelivered($winRecord, ['coupon_id' => $prize->coupon_id]); } /** * 发放红包 */ private static function deliverRedPacket($winRecord, $prize) { // 这里调用红包发放接口 // 示例代码,需要根据实际红包系统实现 LotteryRecordService::markWinRecordAsDelivered($winRecord, ['amount' => $prize->amount]); } /** * 发放兑换码 */ private static function deliverExchangeCode($winRecord, $prize) { $code = static::getAvailableExchangeCode($prize); if ($code) { LotteryRecordService::setWinRecordExchangeCode($winRecord, $code); static::markExchangeCodeUsed($prize, $code); LotteryRecordService::markWinRecordAsDelivered($winRecord, ['exchange_code' => $code]); } else { throw new Exception('兑换码已用完'); } } /** * 发放商城商品 */ private static function deliverGoods($winRecord, $prize) { // 这里可以自动加入购物车或创建订单 // 示例代码,需要根据实际商城系统实现 LotteryRecordService::markWinRecordAsDelivered($winRecord, [ 'goods_id' => $prize->goods_id, 'goods_sku_id' => $prize->goods_sku_id ]); } /** * 更新活动统计 */ private static function updateActivityStats($activity, $isWin) { $activity->total_draw_count += 1; if ($isWin) { $activity->total_win_count += 1; } $activity->save(); } /** * 构建即抽即中的抽奖结果 */ private static function buildDrawResult($drawRecord, $prize, $winRecord = null) { } /** * 获取用户抽奖机会 * @param int $activityId 活动ID * @param int $userId 用户ID * @return array */ public static function getUserChances($activityId, $userId) { $userChance = LotteryChanceService::getUserChance($activityId, $userId); if (!$userChance) { return [ 'total_chances' => 0, 'used_chances' => 0, 'remain_chances' => 0 ]; } return [ 'total_chances' => $userChance->total_chances, 'used_chances' => $userChance->used_chances, 'remain_chances' => $userChance->remain_chances, 'last_get_time' => $userChance->last_get_time, 'last_use_time' => $userChance->last_use_time ]; } // ============ 从LotteryPrize模型移过来的业务逻辑方法 ============ /** * 检查奖品库存是否充足 */ public static function hasPrizeStock(LotteryPrize $prize, $quantity = 1) { return $prize->remain_stock >= $quantity; } /** * 减少奖品库存 */ public static function decreasePrizeStock(LotteryPrize $prize, $quantity = 1) { if (!static::hasPrizeStock($prize, $quantity)) { return false; } $prize->remain_stock -= $quantity; $prize->win_count += $quantity; return $prize->save(); } /** * 获取可用的兑换码 */ public static function getAvailableExchangeCode(LotteryPrize $prize) { if ($prize->type != LotteryEnum::PRIZE_TYPE_CODE) { return null; } $allCodes = $prize->exchange_codes_list; $usedCodes = $prize->used_codes_list; $availableCodes = array_diff($allCodes, $usedCodes); if (empty($availableCodes)) { return null; } return array_shift($availableCodes); } /** * 标记兑换码为已使用 */ public static function markExchangeCodeUsed(LotteryPrize $prize, $code) { $usedCodes = $prize->used_codes_list; if (!in_array($code, $usedCodes)) { $usedCodes[] = $code; $prize->used_codes = json_encode($usedCodes); return $prize->save(); } return true; } /** * 获取有效奖品(库存大于0且状态正常) */ public static function getValidPrizes($activityId) { return LotteryPrize::where('activity_id', $activityId) ->where('status', 1) ->where('remain_stock', '>', 0) ->order('sort_order', 'asc') ->select(); } /** * 检查奖品是否已解锁(按人数解锁功能) */ public static function isPrizeUnlocked(LotteryPrize $prize, $currentPeopleCount) { if (empty($prize->unlock_people_num)) { return true; } return $currentPeopleCount >= $prize->unlock_people_num; } /** * 检查活动是否正在进行 */ public static function isActivityRunning(LotteryActivity $activity) { $now = time(); return $activity->status == LotteryEnum::STATUS_ONGOING && $activity->start_time <= $now && $activity->end_time >= $now; } /** * 检查活动是否已结束 */ public static function isActivityEnded(LotteryActivity $activity) { $now = time(); return $activity->status == LotteryEnum::STATUS_ENDED || $activity->end_time < $now; } /** * 检查活动是否未开始 */ public static function isActivityNotStarted(LotteryActivity $activity) { $now = time(); return $activity->status == LotteryEnum::STATUS_NOT_STARTED && $activity->start_time > $now; } /** * 检查活动是否已暂停 */ public static function isActivitySuspended(LotteryActivity $activity) { return $activity->status == LotteryEnum::STATUS_SUSPENDED; } /** * 检查活动是否已取消 */ public static function isActivityCancelled(LotteryActivity $activity) { return $activity->status == LotteryEnum::STATUS_CANCELLED; } // TODO: 分布式锁相关方法(暂时移除,后续实现) /* private static function acquireLock($lockKey, $expireTime = 10) { // Redis锁获取逻辑 } private static function releaseLock($lockKey) { // Redis锁释放逻辑 } private static function acquireFileLock($lockKey, $expireTime = 10) { // 文件锁获取逻辑 } private static function releaseFileLock($lockKey) { // 文件锁释放逻辑 } */ /** * 获取正在进行的活动 */ public static function getRunningActivities() { $now = time(); return LotteryActivity::where('status', LotteryEnum::STATUS_ONGOING) ->where('start_time', '<=', $now) ->where('end_time', '>=', $now) ->select(); } /** * 获取未开始的活动 */ public static function getNotStartedActivities() { $now = time(); return LotteryActivity::where('status', LotteryEnum::STATUS_NOT_STARTED) ->where('start_time', '>', $now) ->select(); } /** * 获取已结束的活动 */ public static function getEndedActivities() { $now = time(); return LotteryActivity::where('status', LotteryEnum::STATUS_ENDED) ->orWhere('end_time', '<', $now) ->select(); } /** * 获取可显示的活动(排除逻辑状态) */ public static function getDisplayableActivities() { $displayableStatuses = array_keys(LotteryEnum::getActivityStatusMap()); return LotteryActivity::whereIn('status', $displayableStatuses)->select(); } /** * 验证活动状态是否有效 */ public static function isValidActivityStatus(LotteryActivity $activity) { return LotteryEnum::isValidActivityStatus($activity->status); } /** * 验证开奖方式是否有效 */ public static function isValidLotteryType(LotteryActivity $activity) { return LotteryEnum::isValidLotteryType($activity->lottery_type); } }