table('agent.dbo.holiday_wheel_activity') ->orderBy('id', 'desc') ->first(); $now = now(); $status = 0; $startTime = null; $endTime = null; $iconUrl = ''; // 默认规则从活动配置 + 商城档位中计算(包含金额、次数和档位信息) $rechargeRules = $this->getDefaultRechargeRules(); if ($activity) { $status = (int)($activity->status ?? 0); $startTime = $activity->start_time; $endTime = $activity->end_time; $iconUrl = $activity->icon_url ?? ''; // 如果当前时间不在活动时间范围内,则前端状态强制视为关闭(0) if (!empty($startTime) && $now->lt($startTime)) { $status = 0; } if (!empty($endTime) && $now->gt($endTime)) { $status = 0; } } // 前端展示时只需要简单的奖励值数组,例如 [177, 1, 0.2, ...],隐藏 slot_index 和 weight $slots = DB::connection('write') ->table('agent.dbo.holiday_wheel_config') ->orderBy('sort_index', 'asc') ->orderBy('slot_index', 'asc') ->pluck('reward') ->map(function ($reward) { return (float)$reward; }) ->values() ->toArray(); $userTimes = DB::connection('write') ->table('agent.dbo.holiday_wheel_user') ->where('UserID', $userId) ->first(); $leftTimes = $userTimes ? (int)$userTimes->left_times : 0; $totalTimes = $userTimes ? (int)$userTimes->total_times : 0; return [ 'status' => $status, 'start_time' => $startTime, 'end_time' => $endTime, 'icon_url' => $iconUrl, 'slots' => $slots, 'user' => [ 'left_times' => $leftTimes, 'total_times' => $totalTimes, ], 'recharge_rules' => $rechargeRules, 'now' => $now->toDateTimeString(), ]; } /** * 充值赠送转盘次数(在充值成功后调用) * 有 gift_id(首充礼包301,破产礼包302)的充值不添加次数 */ public function grantTimesOnRecharge(int $userId, float $payAmt, int $giftId = 0): void { if (in_array($giftId, [301, 302], true)) { return; } $activity = DB::connection('write') ->table('agent.dbo.holiday_wheel_activity') ->orderBy('id', 'desc') ->first(); if (!$activity || (int)$activity->status !== 1) { return; } $now = now(); if (!empty($activity->start_time) && $now->lt($activity->start_time)) { return; } if (!empty($activity->end_time) && $now->gt($activity->end_time)) { return; } // 统一使用默认规则(已基于活动配置 + 商城档位计算),保持金额与 times 一致 $rules = $this->getDefaultRechargeRules(); $payStr = number_format($payAmt, 2, '.', ''); $addTimes = 0; foreach ($rules as $rule) { $amount = isset($rule['amount']) ? number_format($rule['amount'], 2, '.', '') : null; $times = (int)($rule['times'] ?? 0); if ($amount !== null && $amount === $payStr && $times > 0) { $addTimes += $times; } } if ($addTimes <= 0) { return; } DB::connection('write')->transaction(function () use ($userId, $addTimes) { $row = DB::connection('write') ->table('agent.dbo.holiday_wheel_user') ->where('UserID', $userId) ->lockForUpdate() ->first(); if ($row) { DB::connection('write') ->table('agent.dbo.holiday_wheel_user') ->where('UserID', $userId) ->update([ 'left_times' => (int)$row->left_times + $addTimes, 'total_times' => (int)$row->total_times + $addTimes, 'updated_at' => now(), ]); } else { DB::connection('write') ->table('agent.dbo.holiday_wheel_user') ->insert([ 'UserID' => $userId, 'left_times' => $addTimes, 'total_times' => $addTimes, 'created_at' => now(), 'updated_at' => now(), ]); } }); } /** * 执行一次转盘抽奖 */ public function spin(int $userId): array { $redisKey = 'holiday_wheel_spin_' . $userId; $lock = SetNXLock::getExclusiveLock($redisKey, 5); if (!$lock) { throw new \RuntimeException('操作太频繁,请稍后再试'); } try { $activity = DB::connection('write') ->table('agent.dbo.holiday_wheel_activity') ->orderBy('id', 'desc') ->first(); if (!$activity || (int)$activity->status !== 1) { throw new \RuntimeException('活动未开启'); } $now = now(); if (!empty($activity->start_time) && $now->lt($activity->start_time)) { throw new \RuntimeException('活动未开始'); } if (!empty($activity->end_time) && $now->gt($activity->end_time)) { throw new \RuntimeException('活动已结束'); } $slots = DB::connection('write') ->table('agent.dbo.holiday_wheel_config') ->orderBy('sort_index', 'asc') ->orderBy('slot_index', 'asc') ->get(); if ($slots->isEmpty()) { throw new \RuntimeException('转盘配置不存在'); } DB::connection('write')->beginTransaction(); $userRow = DB::connection('write') ->table('agent.dbo.holiday_wheel_user') ->where('UserID', $userId) ->lockForUpdate() ->first(); if (!$userRow || (int)$userRow->left_times <= 0) { DB::connection('write')->rollBack(); throw new \RuntimeException('没有可用的转盘次数'); } $totalWeight = 0; $weights = []; foreach ($slots as $slot) { $w = (int)$slot->weight; if ($w < 0) { $w = 0; } $weights[] = $w; $totalWeight += $w; } if ($totalWeight <= 0) { DB::connection('write')->rollBack(); throw new \RuntimeException('转盘权重配置错误'); } $rand = mt_rand(1, $totalWeight); $cumulative = 0; $selectedIndex = 0; foreach ($slots as $idx => $slot) { $cumulative += $weights[$idx]; if ($rand <= $cumulative) { $selectedIndex = $idx; break; } } $selectedSlot = $slots[$selectedIndex]; $reward = (float)$selectedSlot->reward; $slotIndex = (int)$selectedSlot->slot_index; DB::connection('write') ->table('agent.dbo.holiday_wheel_user') ->where('UserID', $userId) ->update([ 'left_times' => (int)$userRow->left_times - 1, 'updated_at' => now(), ]); DB::connection('write') ->table('agent.dbo.holiday_wheel_history') ->insert([ 'UserID' => $userId, 'slot_index' => $slotIndex, 'reward' => $reward, 'created_at' => now(), ]); DB::connection('write')->commit(); return [ 'slot_index' => $slotIndex, 'reward' => $reward, 'left_times' => (int)$userRow->left_times - 1, ]; } catch (\Throwable $e) { DB::connection('write')->rollBack(); throw $e; } finally { SetNXLock::release($redisKey); } } /** * 默认充值规则: * - 基于固定金额和次数:[9.99=>1次, 19.99=>3次, 49.99=>6次] * - 额外从 recharge_gear 中取出相同金额的档位详情(类似商城列表的数据), * times 字段保持不变。 */ public function getDefaultRechargeRules(): array { // 固定的基础规则(金额+次数) // 1) 先从活动配置中读取 recharge_rules $rules = []; $activity = DB::connection('write') ->table('agent.dbo.holiday_wheel_activity') ->orderBy('id', 'desc') ->first(); if ($activity && !empty($activity->recharge_rules)) { $decoded = json_decode($activity->recharge_rules, true); if (is_array($decoded)) { $rules = $decoded; } } // 2) 如果没有配置,则使用默认规则 if (empty($rules)) { $rules = [ ['amount' => 9.99, 'times' => 1], ['amount' => 19.99, 'times' => 3], ['amount' => 19.99, 'times' => 3], ['amount' => 49.99, 'times' => 6], ]; } // 为每条规则附加与金额相同的 recharge_gear 配置(类似商城 gear 列表) foreach ($rules as &$rule) { $amount = $rule['amount']; $item = DB::connection('write') ->table('agent.dbo.recharge_gear') ->where('status', 1) // ->where('in_shop', 1) ->where('money', $amount) ->select('id', 'money', 'favorable_price', 'give', 'recommend', 'gear') ->orderBy('id', 'asc') ->first(); if ($item) { // 过滤 gear 支付方式,跟商城接口一致 if (!empty($item->gear)) { $item->gear = Util::filterGearByDevice($item->gear); } $rule['gear'] = [ 'id' => $item->id, 'money' => (float)$item->money, 'favorable_price' => (float)$item->favorable_price, 'give' => (float)$item->give, 'recommend' => (int)($item->recommend ?? 0), 'gear' => $item->gear, ]; } else { $rule['gear'] = null; } } unset($rule); return $rules; } }