|
|
@@ -0,0 +1,643 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Services;
|
|
|
+
|
|
|
+use App\Facade\TableName;
|
|
|
+use App\Game\Services\OuroGameService;
|
|
|
+use App\Http\helper\NumConfig;
|
|
|
+use Carbon\Carbon;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+use Illuminate\Support\Facades\Redis;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Superball Activity: recharge + turnover task, balls, lucky number, prize pool.
|
|
|
+ * All amounts in internal units (NUM_VALUE) where needed for DB/score.
|
|
|
+ */
|
|
|
+class SuperballActivityService
|
|
|
+{
|
|
|
+ public const TIER_MAX = 'A';
|
|
|
+ public const MULTIPLIER_MIN = 1.0;
|
|
|
+ public const MULTIPLIER_MAX = 3.0;
|
|
|
+ public const MULTIPLIER_STEP = 0.5;
|
|
|
+ public const LUCKY_REWARD_PER_BALL = 10; // display unit per matching ball
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get full activity info: yesterday data, today data, tiers, user task, multiplier.
|
|
|
+ */
|
|
|
+ public function getInfo(int $userId): array
|
|
|
+ {
|
|
|
+ $today = Carbon::today()->format('Y-m-d');
|
|
|
+ $yesterday = Carbon::yesterday()->format('Y-m-d');
|
|
|
+
|
|
|
+ $tiers = $this->getTierConfig();
|
|
|
+ $yesterdayDaily = $this->getOrCreateDaily($yesterday);
|
|
|
+ $todayDaily = $this->getOrCreateDaily($today);
|
|
|
+ $userTask = $this->getUserTask($userId, $today);
|
|
|
+ $multiplierRow = $this->getUserMultiplier($userId);
|
|
|
+
|
|
|
+ $rechargeToday = $this->getUserRechargeForDate($userId, $today);
|
|
|
+ $turnoverToday = $this->getUserTotalBetForDate($userId, $today);
|
|
|
+ $turnoverProgress = (int) $turnoverToday;
|
|
|
+
|
|
|
+ $tierConfig = $userTask ? $this->getTierConfigByTier($userTask->tier) : null;
|
|
|
+ $rechargeRequired = $tierConfig ? (int) $tierConfig->recharge_required : 0;
|
|
|
+ $turnoverRequired = $tierConfig ? (int) $tierConfig->turnover_required : 0;
|
|
|
+ $rechargeDisplay = $rechargeToday;
|
|
|
+ $turnoverDisplay = $turnoverProgress / NumConfig::NUM_VALUE;
|
|
|
+ $taskCompleted = $tierConfig && $rechargeDisplay >= $rechargeRequired && $turnoverDisplay >= $turnoverRequired;
|
|
|
+ $canUpgrade = $userTask && $userTask->tier !== self::TIER_MAX && $taskCompleted;
|
|
|
+ $canClaim = $taskCompleted && $userTask && (int) $userTask->status === 0;
|
|
|
+
|
|
|
+ $yesterdayBalls = $this->getUserBalls($userId, $yesterday);
|
|
|
+ $yesterdayLucky = (int) ($yesterdayDaily->lucky_number ?? 0);
|
|
|
+ $yesterdayPrizeLog = $this->getUserPrizeLog($userId, $yesterday);
|
|
|
+ $yesterdayBasePerBall = 0;
|
|
|
+ if ($yesterdayDaily->total_balls > 0 && $yesterdayDaily->pool_amount > 0) {
|
|
|
+ $yesterdayBasePerBall = (int) ($yesterdayDaily->pool_amount / $yesterdayDaily->total_balls);
|
|
|
+ }
|
|
|
+ $yesterdayMyPrize = $yesterdayPrizeLog ? (int) $yesterdayPrizeLog->total_amount : 0;
|
|
|
+ $yesterdayMultiplier = $yesterdayPrizeLog ? (float) $yesterdayPrizeLog->multiplier : 1.0;
|
|
|
+ $canClaimYesterday = false;
|
|
|
+ $yesterdayPendingPrize = 0;
|
|
|
+ if (!$yesterdayPrizeLog && count($yesterdayBalls) > 0) {
|
|
|
+ $canClaimYesterday = true;
|
|
|
+ $yesterdayPendingPrize = $this->calculateYesterdayPrizeForUser($userId, $yesterday);
|
|
|
+ }
|
|
|
+
|
|
|
+ $todayBasePerBall = 0;
|
|
|
+ if ($todayDaily->total_balls > 0 && $todayDaily->pool_amount > 0) {
|
|
|
+ $todayBasePerBall = (int) ($todayDaily->pool_amount / $todayDaily->total_balls);
|
|
|
+ }
|
|
|
+
|
|
|
+ $todayBalls = $this->getUserBalls($userId, $today);
|
|
|
+ $todayMyBallsList = array_map(function ($b) {
|
|
|
+ return ['ball_index' => (int) $b->ball_index, 'number' => (int) $b->number];
|
|
|
+ }, $todayBalls);
|
|
|
+ $todayNumberCounts = [];
|
|
|
+ foreach ($todayBalls as $b) {
|
|
|
+ $n = (int) $b->number;
|
|
|
+ $todayNumberCounts[$n] = ($todayNumberCounts[$n] ?? 0) + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 0 点到 1 点前,前端展示的今日整体数据全部置为 0(不影响实际统计与任务进度)
|
|
|
+ $hour = (int) Carbon::now()->format('G'); // 0-23
|
|
|
+ if ($hour < 1) {
|
|
|
+ $todayDisplayPoolAmount = 0;
|
|
|
+ $todayDisplayCompleted = 0;
|
|
|
+ $todayDisplayTotalBalls = 0;
|
|
|
+ $todayDisplayBasePerBall = 0;
|
|
|
+ $todayDisplayMyBalls = [];
|
|
|
+ $todayDisplayNumberCounts = [];
|
|
|
+ } else {
|
|
|
+ $todayDisplayPoolAmount = (int) $todayDaily->pool_amount;
|
|
|
+ $todayDisplayCompleted = (int) ($todayDaily->completed_count ?? 0);
|
|
|
+ $todayDisplayTotalBalls = (int) ($todayDaily->total_balls ?? 0);
|
|
|
+ $todayDisplayBasePerBall = $todayBasePerBall;
|
|
|
+ $todayDisplayMyBalls = $todayMyBallsList;
|
|
|
+ $todayDisplayNumberCounts = $todayNumberCounts;
|
|
|
+ }
|
|
|
+
|
|
|
+ $last7Lucky = $this->getLast7DaysLuckyNumbersPrivate();
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'yesterday' => [
|
|
|
+ 'pool_amount' => (int) $yesterdayDaily->pool_amount,
|
|
|
+ 'pool_amount_display' => (int) $yesterdayDaily->pool_amount / NumConfig::NUM_VALUE,
|
|
|
+ 'base_reward_per_ball' => $yesterdayBasePerBall,
|
|
|
+ 'base_reward_per_ball_display' => $yesterdayBasePerBall / NumConfig::NUM_VALUE,
|
|
|
+ 'lucky_number' => $yesterdayLucky,
|
|
|
+ 'completed_count' => (int) ($yesterdayDaily->completed_count ?? 0),
|
|
|
+ 'total_balls' => (int) ($yesterdayDaily->total_balls ?? 0),
|
|
|
+ 'my_balls' => $yesterdayBalls,
|
|
|
+ 'my_balls_with_lucky' => array_map(function ($b) use ($yesterdayLucky) {
|
|
|
+ return ['number' => (int) $b->number, 'is_lucky' => (int) $b->number === $yesterdayLucky];
|
|
|
+ }, $yesterdayBalls),
|
|
|
+ 'my_prize' => $yesterdayMyPrize,
|
|
|
+ 'my_prize_display' => $yesterdayMyPrize / NumConfig::NUM_VALUE,
|
|
|
+ 'my_multiplier' => $yesterdayMultiplier,
|
|
|
+ 'can_claim_yesterday' => $canClaimYesterday,
|
|
|
+ 'pending_prize' => $yesterdayPendingPrize,
|
|
|
+ 'pending_prize_display' => $yesterdayPendingPrize / NumConfig::NUM_VALUE,
|
|
|
+ ],
|
|
|
+ 'today' => [
|
|
|
+ 'pool_amount' => $todayDisplayPoolAmount,
|
|
|
+ 'pool_amount_display' => $todayDisplayPoolAmount / NumConfig::NUM_VALUE,
|
|
|
+ 'completed_count' => $todayDisplayCompleted,
|
|
|
+ 'total_balls' => $todayDisplayTotalBalls,
|
|
|
+ 'base_reward_per_ball' => $todayDisplayBasePerBall,
|
|
|
+ 'base_reward_per_ball_display' => $todayDisplayBasePerBall / NumConfig::NUM_VALUE,
|
|
|
+ 'my_balls' => $todayDisplayMyBalls,
|
|
|
+ 'number_counts' => $todayDisplayNumberCounts,
|
|
|
+ ],
|
|
|
+ 'tiers' => $tiers,
|
|
|
+ 'user_task' => $userTask ? [
|
|
|
+ 'tier' => $userTask->tier,
|
|
|
+ 'recharge_required' => $rechargeRequired,
|
|
|
+ 'turnover_required' => $turnoverRequired,
|
|
|
+ 'recharge_progress' => $rechargeDisplay,
|
|
|
+ 'turnover_progress' => $turnoverDisplay,
|
|
|
+ 'task_completed' => $taskCompleted,
|
|
|
+ 'status' => (int) $userTask->status,
|
|
|
+ 'can_claim' => $canClaim,
|
|
|
+ 'can_upgrade' => $canUpgrade,
|
|
|
+ 'ball_count' => $tierConfig ? (int) $tierConfig->ball_count : 0,
|
|
|
+ ] : null,
|
|
|
+ 'multiplier' => [
|
|
|
+ 'value' => (float) ($multiplierRow->multiplier ?? 1.0),
|
|
|
+ 'consecutive_days' => (int) ($multiplierRow->consecutive_days ?? 0),
|
|
|
+ 'min' => self::MULTIPLIER_MIN,
|
|
|
+ 'max' => self::MULTIPLIER_MAX,
|
|
|
+ 'step' => self::MULTIPLIER_STEP,
|
|
|
+ ],
|
|
|
+ 'lucky_reward_per_ball' => self::LUCKY_REWARD_PER_BALL,
|
|
|
+ 'lucky_numbers_7_days' => $last7Lucky,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Select task tier for today (with optional confirm). Creates or updates user task.
|
|
|
+ * @return array success: ['success' => true] | failure: ['success' => false, 'message' => ['key', 'fallback']]
|
|
|
+ */
|
|
|
+ public function selectTier(int $userId, string $tier): array
|
|
|
+ {
|
|
|
+ $today = Carbon::today()->format('Y-m-d');
|
|
|
+ $config = $this->getTierConfigByTier($tier);
|
|
|
+ if (!$config) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.activity_not_found', 'Activity not found']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $existing = $this->getUserTask($userId, $today);
|
|
|
+ if ($existing) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.tier_already_selected', 'Already selected tier for today']];
|
|
|
+ }
|
|
|
+
|
|
|
+ DB::connection('write')->table(TableName::agent() . 'superball_user_task')->insert([
|
|
|
+ 'user_id' => $userId,
|
|
|
+ 'task_date' => $today,
|
|
|
+ 'tier' => $tier,
|
|
|
+ 'total_bet_snapshot' => 0,
|
|
|
+ 'status' => 0,
|
|
|
+ 'created_at' => now()->format('Y-m-d H:i:s'),
|
|
|
+ 'updated_at' => now()->format('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+ return ['success' => true];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Upgrade to higher tier (keep progress). Only allowed when task completed and not A.
|
|
|
+ * @return array success: ['success' => true] | failure: ['success' => false, 'message' => [...]]
|
|
|
+ */
|
|
|
+ public function upgradeTier(int $userId, string $newTier): array
|
|
|
+ {
|
|
|
+ $today = Carbon::today()->format('Y-m-d');
|
|
|
+ $task = $this->getUserTask($userId, $today);
|
|
|
+ if (!$task) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.no_task_today', 'No task for today']];
|
|
|
+ }
|
|
|
+ if ((int) $task->status === 1) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.already_claimed', 'Already claimed']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $tierOrder = ['E' => 1, 'D' => 2, 'C' => 3, 'B' => 4, 'A' => 5];
|
|
|
+ $currentOrder = $tierOrder[$task->tier] ?? 0;
|
|
|
+ $newOrder = $tierOrder[$newTier] ?? 0;
|
|
|
+ if ($newOrder <= $currentOrder) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.cannot_downgrade', 'Can only upgrade to higher tier']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $rechargeToday = $this->getUserRechargeForDate($userId, $today);
|
|
|
+ $turnoverToday = $this->getUserTotalBetForDate($userId, $today);
|
|
|
+ $newConfig = $this->getTierConfigByTier($task->tier);
|
|
|
+ $rechargeOk = $rechargeToday >= (int) $newConfig->recharge_required;
|
|
|
+ $turnoverOk = ($turnoverToday / NumConfig::NUM_VALUE) >= (int) $newConfig->turnover_required;
|
|
|
+
|
|
|
+// var_dump($rechargeToday,$turnoverToday,$newConfig);
|
|
|
+ if (!$rechargeOk || !$turnoverOk) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.task_not_completed', 'Task not completed']];
|
|
|
+ }
|
|
|
+
|
|
|
+ DB::connection('write')->table(TableName::agent() . 'superball_user_task')
|
|
|
+ ->where('user_id', $userId)
|
|
|
+ ->where('task_date', $today)
|
|
|
+ ->update(['tier' => $newTier, 'updated_at' => now()->format('Y-m-d H:i:s')]);
|
|
|
+ return ['success' => true,'user_id' => $userId, 'new_tier' => $newTier,'task_date'=>$today];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Claim reward: grant balls, update daily total_balls/completed_count, update multiplier, mark task claimed.
|
|
|
+ * @return array success: ['ball_count' => n, 'message' => ...] | failure: ['success' => false, 'message' => [...]]
|
|
|
+ */
|
|
|
+ public function claimReward(int $userId): array
|
|
|
+ {
|
|
|
+ $today = Carbon::today()->format('Y-m-d');
|
|
|
+ $task = $this->getUserTask($userId, $today);
|
|
|
+ if (!$task) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.no_task_today', 'No task for today']];
|
|
|
+ }
|
|
|
+ if ((int) $task->status === 1) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.already_claimed', 'Already claimed']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $tierConfig = $this->getTierConfigByTier($task->tier);
|
|
|
+ if (!$tierConfig) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.activity_not_found', 'Activity not found']];
|
|
|
+ }
|
|
|
+ $rechargeToday = $this->getUserRechargeForDate($userId, $today);
|
|
|
+ $turnoverToday = $this->getUserTotalBetForDate($userId, $today);
|
|
|
+ $rechargeOk = ($rechargeToday) >= (int) $tierConfig->recharge_required;
|
|
|
+ $turnoverOk = ($turnoverToday / NumConfig::NUM_VALUE) >= (int) $tierConfig->turnover_required;
|
|
|
+ if (!$rechargeOk || !$turnoverOk) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.task_not_completed', 'Task not completed']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $ballCount = (int) $tierConfig->ball_count;
|
|
|
+
|
|
|
+ DB::connection('write')->transaction(function () use ($userId, $today, $task, $ballCount) {
|
|
|
+ DB::connection('write')->table(TableName::agent() . 'superball_user_task')
|
|
|
+ ->where('user_id', $userId)
|
|
|
+ ->where('task_date', $today)
|
|
|
+ ->update(['status' => 1, 'updated_at' => now()->format('Y-m-d H:i:s')]);
|
|
|
+
|
|
|
+ $daily = DB::connection('write')->table(TableName::agent() . 'superball_daily')
|
|
|
+ ->where('pool_date', $today)
|
|
|
+ ->lockForUpdate()
|
|
|
+ ->first();
|
|
|
+ if ($daily) {
|
|
|
+ DB::connection('write')->table(TableName::agent() . 'superball_daily')
|
|
|
+ ->where('pool_date', $today)
|
|
|
+ ->update([
|
|
|
+ 'total_balls' => (int) $daily->total_balls + $ballCount,
|
|
|
+ 'completed_count' => (int) $daily->completed_count + 1,
|
|
|
+ 'updated_at' => now()->format('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->updateUserMultiplier($userId, $today);
|
|
|
+ });
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'ball_count' => $ballCount,
|
|
|
+ 'message' => 'Claim success, please select numbers for your balls',
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Submit numbers for all balls (0-9 per ball). Must have claimed and not yet submitted.
|
|
|
+ * @return array success: ['success' => true] | failure: ['success' => false, 'message' => [...]]
|
|
|
+ */
|
|
|
+ public function submitNumbers(int $userId, array $numbers): array
|
|
|
+ {
|
|
|
+ $today = Carbon::today()->format('Y-m-d');
|
|
|
+ $task = $this->getUserTask($userId, $today);
|
|
|
+ if (!$task || (int) $task->status !== 1) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.no_claimed_task', 'No claimed task for today']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $tierConfig = $this->getTierConfigByTier($task->tier);
|
|
|
+ if (!$tierConfig) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.activity_not_found', 'Activity not found']];
|
|
|
+ }
|
|
|
+ $ballCount = (int) $tierConfig->ball_count;
|
|
|
+ if (count($numbers) !== $ballCount) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.number_count_mismatch', 'Number count mismatch']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $existing = DB::connection('write')->table(TableName::agent() . 'superball_user_balls')
|
|
|
+ ->where('user_id', $userId)
|
|
|
+ ->where('ball_date', $today)
|
|
|
+ ->count();
|
|
|
+ if ($existing > 0) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.numbers_already_submitted', 'Numbers already submitted']];
|
|
|
+ }
|
|
|
+
|
|
|
+ DB::connection('write')->transaction(function () use ($userId, $today, $numbers) {
|
|
|
+ // 写入用户今日所有球号
|
|
|
+ foreach ($numbers as $index => $num) {
|
|
|
+ $n = (int) $num;
|
|
|
+ if ($n < 0 || $n > 9) {
|
|
|
+ throw new \RuntimeException('Number must be 0-9');
|
|
|
+ }
|
|
|
+ DB::connection('write')->table(TableName::agent() . 'superball_user_balls')->insert([
|
|
|
+ 'user_id' => $userId,
|
|
|
+ 'ball_date' => $today,
|
|
|
+ 'ball_index' => $index + 1,
|
|
|
+ 'number' => $n,
|
|
|
+ 'created_at' => now()->format('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 若用户选择的号码等于当日 lucky_number,则把 superball_daily.lucky_count 累加
|
|
|
+ $daily = DB::connection('write')
|
|
|
+ ->table(TableName::agent() . 'superball_daily')
|
|
|
+ ->where('pool_date', $today)
|
|
|
+ ->lockForUpdate()
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if ($daily) {
|
|
|
+ $luckyNumber = (int) $daily->lucky_number;
|
|
|
+ $matched = 0;
|
|
|
+ foreach ($numbers as $num) {
|
|
|
+ if ((int) $num === $luckyNumber) {
|
|
|
+ $matched++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($matched > 0) {
|
|
|
+ DB::connection('write')
|
|
|
+ ->table(TableName::agent() . 'superball_daily')
|
|
|
+ ->where('pool_date', $today)
|
|
|
+ ->update([
|
|
|
+ 'lucky_count' => (int) $daily->lucky_count + $matched,
|
|
|
+ 'updated_at' => now()->format('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return ['success' => true];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get user's balls for a date (for number selection page or display).
|
|
|
+ */
|
|
|
+ public function getMyBalls(int $userId, string $date): array
|
|
|
+ {
|
|
|
+ $rows = DB::table(TableName::agent() . 'superball_user_balls')
|
|
|
+ ->where('user_id', $userId)
|
|
|
+ ->where('ball_date', $date)
|
|
|
+ ->orderBy('ball_index')
|
|
|
+ ->get();
|
|
|
+ return array_map(function ($r) {
|
|
|
+ return ['ball_index' => (int) $r->ball_index, 'number' => (int) $r->number];
|
|
|
+ }, $rows->all());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * User claims yesterday's reward (next-day claim, no auto distribution).
|
|
|
+ * Formula: base = pool / total_balls * ball_count, lucky = 10 * matched_balls (display), total = base * multiplier + lucky.
|
|
|
+ * @return array success: data with total_amount etc. | failure: ['success' => false, 'message' => [...]]
|
|
|
+ */
|
|
|
+ public function claimYesterdayReward(int $userId): array
|
|
|
+ {
|
|
|
+ $yesterday = Carbon::yesterday()->format('Y-m-d');
|
|
|
+ $existing = $this->getUserPrizeLog($userId, $yesterday);
|
|
|
+ if ($existing) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.yesterday_already_claimed', 'Yesterday reward already claimed']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $balls = $this->getUserBalls($userId, $yesterday);
|
|
|
+ if (count($balls) === 0) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.no_balls_yesterday', 'No balls for yesterday, nothing to claim']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $daily = DB::table(TableName::agent() . 'superball_daily')->where('pool_date', $yesterday)->first();
|
|
|
+ if (!$daily || (int) $daily->total_balls <= 0) {
|
|
|
+ return ['success' => false, 'message' => ['web.superball.pool_not_ready', 'Yesterday pool not ready']];
|
|
|
+ }
|
|
|
+
|
|
|
+ $basePerBall = (int) ($daily->pool_amount / $daily->total_balls);
|
|
|
+ $luckyNumber = (int) $daily->lucky_number;
|
|
|
+ $multiplierRow = $this->getUserMultiplier($userId);
|
|
|
+ $multiplier = (float) ($multiplierRow->multiplier ?? 1.0);
|
|
|
+
|
|
|
+ // 选号可重复:每个球单独比对幸运号,中奖球数 = 号码等于幸运号的球个数(同一号码可多球)
|
|
|
+ $matched = 0;
|
|
|
+ foreach ($balls as $b) {
|
|
|
+ if ((int) $b->number === $luckyNumber) {
|
|
|
+ $matched++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $baseAmount = $basePerBall * count($balls);
|
|
|
+ $luckyAmountDisplay = self::LUCKY_REWARD_PER_BALL * $matched;
|
|
|
+ $luckyAmountInternal = $luckyAmountDisplay * NumConfig::NUM_VALUE;
|
|
|
+ $totalAmount = (int) round($baseAmount * $multiplier) + $luckyAmountInternal;
|
|
|
+
|
|
|
+ DB::connection('write')->table(TableName::agent() . 'superball_prize_log')->insert([
|
|
|
+ 'user_id' => $userId,
|
|
|
+ 'settle_date' => $yesterday,
|
|
|
+ 'base_amount' => $baseAmount,
|
|
|
+ 'lucky_amount' => $luckyAmountInternal,
|
|
|
+ 'multiplier' => $multiplier,
|
|
|
+ 'total_amount' => $totalAmount,
|
|
|
+ 'created_at' => now()->format('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+ OuroGameService::AddScore($userId, $totalAmount, 90);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'total_amount' => $totalAmount,
|
|
|
+ 'total_amount_display' => $totalAmount / NumConfig::NUM_VALUE,
|
|
|
+ 'base_amount' => $baseAmount,
|
|
|
+ 'lucky_amount' => $luckyAmountInternal,
|
|
|
+ 'multiplier' => $multiplier,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Calculate yesterday prize for user (for display only, no claim).
|
|
|
+ */
|
|
|
+ public function calculateYesterdayPrizeForUser(int $userId, string $yesterday): int
|
|
|
+ {
|
|
|
+ $daily = DB::table(TableName::agent() . 'superball_daily')->where('pool_date', $yesterday)->first();
|
|
|
+ if (!$daily || (int) $daily->total_balls <= 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ $balls = $this->getUserBalls($userId, $yesterday);
|
|
|
+ if (count($balls) === 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ $basePerBall = (int) ($daily->pool_amount / $daily->total_balls);
|
|
|
+ $luckyNumber = (int) $daily->lucky_number;
|
|
|
+ $multiplierRow = $this->getUserMultiplier($userId);
|
|
|
+ $multiplier = (float) ($multiplierRow->multiplier ?? 1.0);
|
|
|
+ // 选号可重复:按球逐个比对幸运号,中奖球数 = 号码等于幸运号的球个数
|
|
|
+ $matched = 0;
|
|
|
+ foreach ($balls as $b) {
|
|
|
+ if ((int) $b->number === $luckyNumber) {
|
|
|
+ $matched++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $baseAmount = $basePerBall * count($balls);
|
|
|
+ $luckyAmountInternal = self::LUCKY_REWARD_PER_BALL * $matched * NumConfig::NUM_VALUE;
|
|
|
+ return (int) round($baseAmount * $multiplier) + $luckyAmountInternal;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get last 7 days lucky numbers (for display).
|
|
|
+ */
|
|
|
+ public function getLast7DaysLuckyNumbers(): array
|
|
|
+ {
|
|
|
+ return $this->getLast7DaysLuckyNumbersPrivate();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Ensure daily row and lucky number for date (idempotent).
|
|
|
+ */
|
|
|
+ public function getOrCreateDaily(string $date): \stdClass
|
|
|
+ {
|
|
|
+ $row = DB::table(TableName::agent() . 'superball_daily')->where('pool_date', $date)->first();
|
|
|
+ if ($row) {
|
|
|
+ return $row;
|
|
|
+ }
|
|
|
+ $lucky = mt_rand(0, 9);
|
|
|
+ DB::connection('write')->table(TableName::agent() . 'superball_daily')->insert([
|
|
|
+ 'pool_date' => $date,
|
|
|
+ 'pool_amount' => 0,
|
|
|
+ 'total_balls' => 0,
|
|
|
+ 'lucky_number' => $lucky,
|
|
|
+ 'completed_count' => 0,
|
|
|
+ 'lucky_count' => 0,
|
|
|
+ 'created_at' => now()->format('Y-m-d H:i:s'),
|
|
|
+ 'updated_at' => now()->format('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+ return DB::table(TableName::agent() . 'superball_daily')->where('pool_date', $date)->first();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Update pool amount for a date (call from job that aggregates daily turnover).
|
|
|
+ */
|
|
|
+ public function updatePoolAmount(string $date, int $poolAmountInternal): void
|
|
|
+ {
|
|
|
+ DB::connection('write')->table(TableName::agent() . 'superball_daily')
|
|
|
+ ->where('pool_date', $date)
|
|
|
+ ->update(['pool_amount' => $poolAmountInternal, 'updated_at' => now()->format('Y-m-d H:i:s')]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // --- private helpers ---
|
|
|
+
|
|
|
+ private function getTierConfig(): array
|
|
|
+ {
|
|
|
+ $rows = DB::table(TableName::agent() . 'superball_tier_config')
|
|
|
+ ->orderBy('sort_index')
|
|
|
+ ->get();
|
|
|
+ return array_map(function ($r) {
|
|
|
+ return [
|
|
|
+ 'tier' => $r->tier,
|
|
|
+ 'recharge_required' => (int) $r->recharge_required,
|
|
|
+ 'turnover_required' => (int) $r->turnover_required,
|
|
|
+ 'ball_count' => (int) $r->ball_count,
|
|
|
+ ];
|
|
|
+ }, $rows->all());
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getTierConfigByTier(string $tier): ?\stdClass
|
|
|
+ {
|
|
|
+ return DB::table(TableName::agent() . 'superball_tier_config')->where('tier', $tier)->first();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getUserTask(int $userId, string $date): ?\stdClass
|
|
|
+ {
|
|
|
+ return DB::table(TableName::agent() . 'superball_user_task')
|
|
|
+ ->where('user_id', $userId)
|
|
|
+ ->where('task_date', $date)
|
|
|
+ ->first();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getUserRechargeForDate(int $userId, string $date): int
|
|
|
+ {
|
|
|
+ $dateId = str_replace('-', '', $date);
|
|
|
+ $row = DB::table(TableName::QPRecordDB() . 'RecordUserDataStatisticsNew')
|
|
|
+ ->where('UserID', $userId)
|
|
|
+ ->where('DateID', $dateId)
|
|
|
+ ->first();
|
|
|
+ return $row ? (int) $row->Recharge : 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 当日流水:从按日统计表取当天 TotalBet(内部单位) */
|
|
|
+ private function getUserTotalBetForDate(int $userId, string $date): int
|
|
|
+ {
|
|
|
+ $dateId = str_replace('-', '', $date);
|
|
|
+ $row = DB::table(TableName::QPRecordDB() . 'RecordUserDataStatisticsNew')
|
|
|
+ ->where('UserID', $userId)
|
|
|
+ ->where('DateID', $dateId)
|
|
|
+ ->first();
|
|
|
+ return $row && isset($row->TotalBet) ? (int) $row->TotalBet : 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getUserTotalBet(int $userId): int
|
|
|
+ {
|
|
|
+ $row = DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')
|
|
|
+ ->where('UserID', $userId)
|
|
|
+ ->first();
|
|
|
+ return $row ? (int) $row->TotalBet : 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getUserBalls(int $userId, string $date): array
|
|
|
+ {
|
|
|
+ return DB::table(TableName::agent() . 'superball_user_balls')
|
|
|
+ ->where('user_id', $userId)
|
|
|
+ ->where('ball_date', $date)
|
|
|
+ ->orderBy('ball_index')
|
|
|
+ ->get()
|
|
|
+ ->all();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getUserMultiplier(int $userId): ?\stdClass
|
|
|
+ {
|
|
|
+ return DB::table(TableName::agent() . 'superball_user_multiplier')->where('user_id', $userId)->first();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getUserPrizeLog(int $userId, string $date): ?\stdClass
|
|
|
+ {
|
|
|
+ return DB::table(TableName::agent() . 'superball_prize_log')
|
|
|
+ ->where('user_id', $userId)
|
|
|
+ ->where('settle_date', $date)
|
|
|
+ ->first();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getLast7DaysLuckyNumbersPrivate(): array
|
|
|
+ {
|
|
|
+ $dates = [];
|
|
|
+ for ($i = 0; $i < 7; $i++) {
|
|
|
+ $dates[] = Carbon::today()->subDays($i)->format('Y-m-d');
|
|
|
+ }
|
|
|
+ $rows = DB::table(TableName::agent() . 'superball_daily')
|
|
|
+ ->whereIn('pool_date', $dates)
|
|
|
+ ->orderBy('pool_date', 'desc')
|
|
|
+ ->get();
|
|
|
+ $map = [];
|
|
|
+ foreach ($rows as $r) {
|
|
|
+ $map[$r->pool_date] = (int) $r->lucky_number;
|
|
|
+ }
|
|
|
+ return array_map(function ($d) use ($map) {
|
|
|
+ return ['date' => $d, 'lucky_number' => $map[$d] ?? null];
|
|
|
+ }, $dates);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function updateUserMultiplier(int $userId, string $taskDate): void
|
|
|
+ {
|
|
|
+ $row = DB::connection('write')->table(TableName::agent() . 'superball_user_multiplier')
|
|
|
+ ->where('user_id', $userId)
|
|
|
+ ->lockForUpdate()
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ $multiplier = self::MULTIPLIER_MIN;
|
|
|
+ $consecutive = 1;
|
|
|
+
|
|
|
+ if ($row) {
|
|
|
+ $last = $row->last_task_date ? (string) $row->last_task_date : null;
|
|
|
+ $prev = Carbon::parse($taskDate)->subDay()->format('Y-m-d');
|
|
|
+ if ($last === $prev) {
|
|
|
+ $consecutive = (int) $row->consecutive_days + 1;
|
|
|
+ $multiplier = min(self::MULTIPLIER_MAX, (float) $row->multiplier + self::MULTIPLIER_STEP);
|
|
|
+ } elseif ($last !== null && $last !== $taskDate) {
|
|
|
+ $daysDiff = (int) Carbon::parse($taskDate)->diffInDays(Carbon::parse($last));
|
|
|
+ if ($daysDiff > 1) {
|
|
|
+ $multiplier = max(self::MULTIPLIER_MIN, (float) $row->multiplier - self::MULTIPLIER_STEP);
|
|
|
+ $consecutive = 1;
|
|
|
+ } else {
|
|
|
+ $multiplier = (float) $row->multiplier;
|
|
|
+ $consecutive = (int) $row->consecutive_days + 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ DB::connection('write')->table(TableName::agent() . 'superball_user_multiplier')->updateOrInsert(
|
|
|
+ ['user_id' => $userId],
|
|
|
+ [
|
|
|
+ 'consecutive_days' => $consecutive,
|
|
|
+ 'last_task_date' => $taskDate,
|
|
|
+ 'multiplier' => $multiplier,
|
|
|
+ 'updated_at' => now()->format('Y-m-d H:i:s'),
|
|
|
+ ]
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|