1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114 |
- <?php
- namespace app\common\Service\Lottery;
- use app\common\model\lottery\LotteryActivity;
- use app\common\model\lottery\LotteryPrize;
- use app\common\Enum\LotteryEnum;
- use app\common\exception\BusinessException;
- use app\common\Enum\ErrorCodeEnum;
- use think\Db;
- use think\Exception;
- use think\Cache;
- use app\common\model\lottery\LotteryDrawRecord;
- use app\common\model\User;
- /**
- * 抽奖服务类
- * 核心抽奖逻辑处理
- */
- class LotteryService
- {
- // TODO: 分布式锁相关功能(暂时移除,后续实现)
- /**
- * 当前进程持有的锁信息
- * @var array
- */
- // private static $locks = [];
- /**
- * 执行抽奖
- * @param int $activityId 活动ID
- * @param int $userId 用户ID
- * @param int $triggerType 触发类型
- * @param int $triggerOrderId 触发订单ID
- * @param float $triggerAmount 触发金额
- * @return array 抽奖结果
- * @throws Exception
- */
- public static function drawLottery($activityId, $userId, $triggerType = 1, $triggerOrderId = null, $triggerAmount = null)
- {
- // 1. 验证活动有效性
- $activity = LotteryActivity::find($activityId);
- if (!$activity || !self::isActivityRunning($activity)) {
- throw new BusinessException('活动不存在或未开始', ErrorCodeEnum::LOTTERY_ACTIVITY_NOT_FOUND);
- }
- // 2. 验证开奖时间(仅对按时间开奖有效)
- if ($activity->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. 返回抽奖结果
- return static::buildDrawResult($drawRecord, $selectedPrize, $winRecord);
- } 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;
- case LotteryEnum::PRIZE_TYPE_SHOP_GOODS:
- static::deliverGoods($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)
- {
- $result = [
- 'draw_id' => $drawRecord->id,
- 'is_win' => $drawRecord->is_win,
- 'prize' => [
- 'id' => $prize->id,
- 'name' => $prize->name,
- 'type' => $prize->type,
- 'type_text' => $prize->type_text,
- 'image' => cdnurl($prize->image),
- 'win_prompt' => $prize->win_prompt
- ],
- 'draw_time' => $drawRecord->draw_time
- ];
- if ($winRecord) {
- $result['win_record_id'] = $winRecord->id;
- $result['deliver_status'] = $winRecord->deliver_status;
-
- // 只有兑换码类型的奖品才记录兑换码信息
- if ($prize->type == LotteryEnum::PRIZE_TYPE_CODE) {
- $result['exchange_code'] = $winRecord->exchange_code;
- }
- }
- return $result;
- }
- /**
- * 获取用户抽奖机会
- * @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);
- }
- }
|