laowu 20 ore fa
parent
commit
56b38600eb

+ 3 - 14
app/Console/Commands/SuperballUpdatePoolAndStats.php

@@ -53,18 +53,7 @@ class SuperballUpdatePoolAndStats extends Command
             // 2. 确保当日 superball_daily 记录存在
             DB::connection('write')->transaction(function () use ($dateStr, $poolAmount, $today) {
                 // getOrCreateDaily 已经保证有一条记录(带 lucky_number),此处再加锁更新
-                $daily = DB::connection('write')->table(TableName::agent() . 'superball_daily')
-                    ->where('pool_date', $dateStr)
-                    ->lockForUpdate()
-                    ->first();
-
-                if (!$daily) {
-                    $this->service->getOrCreateDaily($dateStr);
-                    $daily = DB::connection('write')->table(TableName::agent() . 'superball_daily')
-                        ->where('pool_date', $dateStr)
-                        ->lockForUpdate()
-                        ->first();
-                }
+                $daily = $this->service->getOrCreateDaily($dateStr);
 
                 // 3. 计算本次要增加的 completed_count 和 total_balls
                 $hour = (int)$today->format('G'); // 0-23
@@ -86,8 +75,8 @@ class SuperballUpdatePoolAndStats extends Command
                     ->where('pool_date', $dateStr)
                     ->update([
                         'pool_amount' => $poolAmount,
-                        'completed_count' => (int)$daily->completed_count + $completedInc,
-                        'total_balls' => (int)$daily->total_balls + $ballsInc,
+                        'completed_count' => DB::raw("completed_count + {$completedInc}"),
+                        'total_balls' => DB::raw("total_balls + {$ballsInc}"),
                         'updated_at' => now()->format('Y-m-d H:i:s'),
                     ]);
             });

+ 143 - 45
app/Services/SuperballActivityService.php

@@ -16,7 +16,7 @@ use App\Services\VipService;
  */
 class SuperballActivityService
 {
-    public const TIER_MAX = 'A';
+    public const TIER_MAX = 'S';
     public const MULTIPLIER_MIN = 1.0;
     public const MULTIPLIER_MAX = 3.0;
     public const MULTIPLIER_STEP = 0.5;
@@ -51,8 +51,30 @@ class SuperballActivityService
         $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;
+        $canUpgrade = $userTask && $userTask->tier !== self::TIER_MAX && $taskCompleted;
+        // 能升级自动升
+        if ($canUpgrade && $userTask->status != 1) {
+            $tierConfigs = $this->getTierConfig();
+            $up = null;
+            foreach ($tierConfigs as $c) {
+                if ($c['recharge_required'] < $rechargeToday && $c['turnover_required'] < $turnoverDisplay) {
+                    continue;
+                }
+                $up = $c;
+                break;
+            }
+            $res = $this->upgradeTier($userId, $up['tier']);
+            if ($res['success']) {
+                $tierConfig = $this->getTierConfigByTier($res['new_tier']);
+                $userTask = $this->getUserTask($userId, $today);
+                $rechargeRequired = $tierConfig ? (int) $tierConfig->recharge_required : 0;
+                $turnoverRequired = $tierConfig ? (int) $tierConfig->turnover_required : 0;
+                $taskCompleted = $tierConfig && $rechargeDisplay >= $rechargeRequired && $turnoverDisplay >= $turnoverRequired;
+                $canClaim = $taskCompleted && $userTask && (int) $userTask->status === 0;
+            }
+        }
+
 
         $yesterdayBalls = $this->getUserBalls($userId, $yesterday);
         $yesterdayLucky = (int) ($yesterdayDaily->lucky_number ?? 0);
@@ -69,6 +91,8 @@ class SuperballActivityService
             $canClaimYesterday = true;
             $yesterdayPendingPrize = $this->calculateYesterdayPrizeForUser($userId, $yesterday);
         }
+        // 用户昨日未领取球
+        $yesterdayNotClaimedBallCount = $this->getNotClaimBallByUserIdDate($userId, $yesterday);
 
         $todayBasePerBall = 0;
         if ($todayDaily->total_balls > 0 && $todayDaily->pool_amount > 0) {
@@ -124,6 +148,7 @@ class SuperballActivityService
                 'can_claim_yesterday' => $canClaimYesterday,
                 'pending_prize' => $yesterdayPendingPrize,
                 'pending_prize_display' => $yesterdayPendingPrize / NumConfig::NUM_VALUE,
+                'yesterday_not_claimed_ball_count' => $yesterdayNotClaimedBallCount,
             ],
             'today' => [
                 'pool_amount' => $todayDisplayPoolAmount,
@@ -161,6 +186,7 @@ class SuperballActivityService
             ],
             'lucky_reward_per_ball' => self::LUCKY_REWARD_PER_BALL,
             'lucky_numbers_7_days' => $last7Lucky,
+            'can_sumbit' => count($todayDisplayMyBalls) < $vipFreeBalls + ($taskCompleted ? $tierConfig->ball_count : 0)
         ];
 
         return $data;
@@ -227,7 +253,7 @@ class SuperballActivityService
             return ['success' => false, 'message' => ['web.superball.already_claimed', 'Already claimed']];
         }
 
-        $tierOrder = ['E' => 1, 'D' => 2, 'C' => 3, 'B' => 4, 'A' => 5];
+        $tierOrder = ['E' => 1, 'D' => 2, 'C' => 3, 'B' => 4, 'A' => 5, 'S' => 6];
         $currentOrder = $tierOrder[$task->tier] ?? 0;
         $newOrder = $tierOrder[$newTier] ?? 0;
         if ($newOrder <= $currentOrder) {
@@ -260,8 +286,24 @@ class SuperballActivityService
     {
         $today = Carbon::today()->format('Y-m-d');
         $task = $this->getUserTask($userId, $today);
+        $vipLevel = $this->getUserVipLevel($userId);
+        $level = VipService::getVipByField('VIP', $vipLevel);
+        $vipFreeBalls = $level ? ($level->SuperballNum ?? 0) : 0;
         if (!$task) {
-            return ['success' => false, 'message' => ['web.superball.no_task_today', 'No task for today']];
+            // 昨日未领取奖励
+            $yesterday = Carbon::yesterday()->format('Y-m-d');
+            $yesterdayBallCount = $this->getNotClaimBallByUserIdDate($userId, $yesterday);
+            if ($yesterdayBallCount > 0) {
+                $vipFreeBalls += $yesterdayBallCount;
+            }
+            $this->claimIfHasVipReward($userId, $vipFreeBalls);
+            $this->selectTier($userId, 'E');
+            return [
+                'ball_count' => $vipFreeBalls,
+                'base_ball_count' => 0,
+                'vip_free_balls' => $vipFreeBalls,
+                'message' => 'Claim success, please select numbers for your balls',
+            ];
         }
         if ((int) $task->status === 1) {
             return ['success' => false, 'message' => ['web.superball.already_claimed', 'Already claimed']];
@@ -275,80 +317,105 @@ class SuperballActivityService
         $turnoverToday = $this->getUserTotalBetForDate($userId, $today);
         $rechargeOk = ($rechargeToday) >= (int) $tierConfig->recharge_required;
         $turnoverOk = ($turnoverToday / NumConfig::NUM_VALUE) >= (int) $tierConfig->turnover_required;
+        $complete = 1;
         if (!$rechargeOk || !$turnoverOk) {
-            return ['success' => false, 'message' => ['web.superball.task_not_completed', 'Task not completed']];
+            // 本档没完成
+            $complete = 0;
+        }
+        // 所有任务累加
+        $tierConfigs = $this->getTierConfig();
+        $ballCount = 0;
+        foreach ($tierConfigs as $config) {
+            if ($config['sort_index'] > $tierConfig->sort_index) {
+                $ballCount += (int) $config['ball_count'];
+            }
+            if ($config['sort_index'] == $tierConfig->sort_index && $complete) {
+                $ballCount += (int) $config['ball_count'];
+            }
         }
 
-        // 基础任务球数 + 每日 VIP 免费球数(赠送球 = VIP 等级)
-        $baseBallCount = (int) $tierConfig->ball_count;
-        $vipLevel = $this->getUserVipLevel($userId);
-        $level = VipService::getVipByField('VIP', $vipLevel);
-        $vipFreeBalls = $level ? ($level->SuperballNum ?? 0) : 0;
-        $ballCount = $baseBallCount + $vipFreeBalls;
+        if ($ballCount < 1) {
+            return ['success' => false, 'message' => ['web.superball.task_not_completed', 'Task not completed']];
+        }
 
-        DB::connection('write')->transaction(function () use ($userId, $today, $task, $ballCount) {
+        DB::connection('write')->transaction(function () use ($userId, $today, $task, $ballCount, $complete) {
             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')]);
+                ->update(['status' => 1, 'complete' => $complete, 'updated_at' => now()->format('Y-m-d H:i:s')]);
 
-            $daily = DB::connection('write')->table(TableName::agent() . 'superball_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'),
-                    ]);
-            }
+                ->update([
+                    'total_balls' => DB::raw('total_balls+' . $ballCount),
+                    'completed_count' => DB::raw('completed_count+1'),
+                    'updated_at' => now()->format('Y-m-d H:i:s'),
+                ]);
 
             $this->updateUserMultiplier($userId, $today);
         });
 
         return [
             'ball_count' => $ballCount,
-            'base_ball_count' => $baseBallCount,
+            'base_ball_count' => $ballCount,
             'vip_free_balls' => $vipFreeBalls,
             'message' => 'Claim success, please select numbers for your balls',
         ];
     }
 
+    public function claimIfHasVipReward(int $userId, $vipFreeBalls): bool
+    {
+        $key = 'claim_vip_reward_' . $userId;
+        if (Redis::exists($key)) {
+            return false;
+        }
+        $today = Carbon::today()->format('Y-m-d');
+        DB::connection('write')->table(TableName::agent() . 'superball_daily')
+            ->where('pool_date', $today)
+            ->update([
+                'total_balls' => DB::raw('total_balls+' . $vipFreeBalls),
+                'updated_at' => now()->format('Y-m-d H:i:s'),
+            ]);
+        Redis::set($key, $vipFreeBalls);
+        Redis::expireAt($key, strtotime('today +1 day'));
+        return true;
+    }
+
     /**
      * 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
     {
+        $key = 'claim_vip_reward_' . $userId;
+        $vipFreeBalls = (int) Redis::get($key);
         $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']];
-        }
-
+        $ballCount = 0;
         $tierConfig = $this->getTierConfigByTier($task->tier);
-        if (!$tierConfig) {
-            return ['success' => false, 'message' => ['web.superball.activity_not_found', 'Activity not found']];
-        }
-        // 与领取时保持一致:基础任务球数 + 每日 VIP 免费球数
-        $baseBallCount = (int) $tierConfig->ball_count;
-        $vipLevel = $this->getUserVipLevel($userId);
-        $level = VipService::getVipByField('VIP', $vipLevel);
-        $vipFreeBalls = $level ? ($level->SuperballNum ?? 0) : 0;
-        $ballCount = $baseBallCount + $vipFreeBalls;
-        if (count($numbers) !== $ballCount) {
-            return ['success' => false, 'message' => ['web.superball.number_count_mismatch', 'Number count mismatch']];
+        if ($task && $task->status == 1) {
+            $tierConfigs = $this->getTierConfig();
+            foreach ($tierConfigs as $config) {
+                if ($config['sort_index'] > $tierConfig->sort_index) {
+                    $ballCount += (int) $config['ball_count'];
+                }
+                if ($config['sort_index'] == $tierConfig->sort_index && $task->complete) {
+                    $ballCount += (int) $config['ball_count'];
+                }
+            }
         }
 
         $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']];
+        // 与领取时保持一致:基础任务球数 + 每日 VIP 免费球数
+        $ballCount = $ballCount + $vipFreeBalls;
+        if ($ballCount < 1) {
+            return ['success' => false, 'message' => ['web.superball.number_count_mismatch', 'no ball']];
+        }
+        if (count($numbers) + $existing > $ballCount) {
+            return ['success' => false, 'message' => ['web.superball.number_count_mismatch', 'Number count mismatch']];
         }
 
         DB::connection('write')->transaction(function () use ($userId, $today, $numbers) {
@@ -371,7 +438,6 @@ class SuperballActivityService
             $daily = DB::connection('write')
                 ->table(TableName::agent() . 'superball_daily')
                 ->where('pool_date', $today)
-                ->lockForUpdate()
                 ->first();
 
             if ($daily) {
@@ -387,7 +453,7 @@ class SuperballActivityService
                         ->table(TableName::agent() . 'superball_daily')
                         ->where('pool_date', $today)
                         ->update([
-                            'lucky_count' => (int) $daily->lucky_count + $matched,
+                            'lucky_count' => DB::raw("lucky_count + {$matched}"),
                             'updated_at' => now()->format('Y-m-d H:i:s'),
                         ]);
                 }
@@ -542,6 +608,37 @@ class SuperballActivityService
             ->update(['pool_amount' => $poolAmountInternal, 'updated_at' => now()->format('Y-m-d H:i:s')]);
     }
 
+    /**
+     * 获取某天未领取的球数
+     * @param $userId
+     * @param $date
+     * @return int
+     */
+    public function getNotClaimBallByUserIdDate($userId, $date) :int
+    {
+        $cacheKey = sprintf('superball_yesterday_not_claim_%d_%s', $userId, $date);
+        if (Redis::exists($cacheKey)) {
+            $ball = Redis::get($cacheKey);
+            return (int) $ball;
+        }
+        $ball = 0;
+        $task = $this->getUserTask($userId, $date);
+        if (!$task || $task->status == 0) {
+            $recharge = $this->getUserRechargeForDate($userId, $date);
+            $turnover = $this->getUserTotalBetForDate($userId, $date) / NumConfig::NUM_VALUE;
+            $configs = $this->getTierConfig();
+            foreach ($configs as $config) {
+                if ($recharge >= $config['recharge_required'] && $turnover >= $config['turnover_required']) {
+                    $ball += $config['ball_count'];
+                }
+            }
+        }
+        Redis::set($cacheKey, $ball);
+        Redis::expireAt($cacheKey, strtotime('today +1 day'));
+
+        return $ball;
+    }
+
     // --- private helpers ---
 
     private function getTierConfig(): array
@@ -551,6 +648,7 @@ class SuperballActivityService
             ->get();
         return array_map(function ($r) {
             return [
+                'sort_index' => $r->sort_index,
                 'tier' => $r->tier,
                 'recharge_required' => (int) $r->recharge_required,
                 'turnover_required' => (int) $r->turnover_required,