Browse Source

superball活动+后台

Tree 3 weeks ago
parent
commit
327845fa0b

+ 302 - 0
Superball_Activity_API.md

@@ -0,0 +1,302 @@
+# Superball 活跃活动 - 前端接口文档
+
+## 一、通用说明
+
+### 1.1 基础信息
+
+- **接口前缀**:与现有 Game 接口一致(需带登录态)
+- **请求方式**:支持 `GET` / `POST`(均可,建议 POST 传参)
+- **鉴权**:所有接口需在「已登录」状态下调用(`checkGameLogin` + `mustGameLogin`)
+
+### 1.2 统一响应格式
+
+**成功:**
+
+```json
+{
+  "data": { /* 业务数据 */ },
+  "result": "",
+  "msg": "success",
+  "code": 200
+}
+```
+
+**失败:**
+
+```json
+{
+  "data": [],
+  "msg": "错误信息或多语言 key",
+  "code": 301
+}
+```
+
+- 业务数据在 **`data`** 中,前端以 `res.data` 取用。
+- `code !== 200` 时按失败处理,`msg` 可做提示或走多语言。
+
+---
+
+## 二、接口列表
+
+### 2.1 获取活动完整信息
+
+**用途**:进入活动页时调用,拿到昨日数据、今日数据、档位配置、当前任务进度、系数、近 7 天幸运号等,用于整页展示与按钮状态。
+
+| 项目 | 说明 |
+|------|------|
+| 路径 | `/superball/info` |
+| 方法 | GET / POST |
+| 请求参数 | 无(用户身份从登录态取) |
+
+**成功时 `data` 结构:**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| yesterday | object | 昨日相关数据 |
+| yesterday.pool_amount | number | 昨日奖池(内部单位,一般用 display) |
+| yesterday.pool_amount_display | number | 昨日奖池展示金额 |
+| yesterday.base_reward_per_ball | number | 昨日每球基础奖励(内部单位) |
+| yesterday.base_reward_per_ball_display | number | 昨日每球基础奖励展示金额 |
+| yesterday.lucky_number | number | 昨日幸运号码(0-9) |
+| yesterday.completed_count | number | 昨日完成人数 |
+| yesterday.total_balls | number | 昨日总奖励球数 |
+| yesterday.my_balls | array | 昨日我拥有的球(原始对象列表) |
+| yesterday.my_balls_with_lucky | array | 昨日我的球及是否中幸运号,项:`{ number, is_lucky }` |
+| yesterday.my_prize | number | 昨日已领奖金(内部单位),未领为 0 |
+| yesterday.my_prize_display | number | 昨日已领奖金展示金额 |
+| yesterday.my_multiplier | number | 昨日结算时使用的奖励系数 |
+| yesterday.can_claim_yesterday | boolean | 是否可以领取昨日奖金(次日领一次) |
+| yesterday.pending_prize | number | 待领取昨日奖金(内部单位),仅 can_claim_yesterday 为 true 时有意义 |
+| yesterday.pending_prize_display | number | 待领取昨日奖金展示金额 |
+| today | object | 今日实时数据 |
+| today.pool_amount | number | 今日奖池(内部单位) |
+| today.pool_amount_display | number | 今日奖池展示金额 |
+| today.completed_count | number | 今日已完成人数 |
+| today.total_balls | number | 今日当前总奖励球数 |
+| today.base_reward_per_ball | number | 今日当前每球基础奖励(内部单位) |
+| today.base_reward_per_ball_display | number | 今日当前每球基础奖励展示金额 |
+| today.my_balls | array | 今日我选的球及号码,项:`{ ball_index, number }`;未选号为空数组 `[]` |
+| today.number_counts | object | 每个号码被选的次数,如 `{"3": 2, "7": 1}` 表示 3 选了 2 次、7 选了 1 次;未选号为空对象 `{}` |
+| tiers | array | 档位配置列表 |
+| tiers[].tier | string | 档位:A / B / C / D / E |
+| tiers[].recharge_required | number | 该档充值要求(金额) |
+| tiers[].turnover_required | number | 该档流水要求(金额) |
+| tiers[].ball_count | number | 该档奖励球数 |
+| user_task | object \| null | 今日任务,未选档则为 null |
+| user_task.tier | string | 当前档位 |
+| user_task.recharge_required | number | 当前档充值要求 |
+| user_task.turnover_required | number | 当前档流水要求 |
+| user_task.recharge_progress | number | 当日已达成充值金额(仅统计当天) |
+| user_task.turnover_progress | number | 当日已达成流水金额(仅统计当天,非累计) |
+| user_task.task_completed | boolean | 今日任务是否已完成(充值+流水都达标) |
+| user_task.status | number | 0=未领球,1=已领球 |
+| user_task.can_claim | boolean | 是否可领取今日奖励(领球) |
+| user_task.can_upgrade | boolean | 是否可升级到更高档位 |
+| user_task.ball_count | number | 当前档可获得球数 |
+| multiplier | object | 当前用户系数相关 |
+| multiplier.value | number | 当前奖励系数(1~3) |
+| multiplier.consecutive_days | number | 连续完成任务天数 |
+| multiplier.min | number | 系数下限(常量 1) |
+| multiplier.max | number | 系数上限(常量 3) |
+| multiplier.step | number | 系数步长(常量 0.5) |
+| lucky_reward_per_ball | number | 每个中奖球对应的幸运号奖励金额(展示单位,常量 10);选号可重复,多个球选同一号码则按球数累加 |
+| lucky_numbers_7_days | array | 近 7 天幸运号码,项:`{ date, lucky_number }` |
+
+---
+
+### 2.2 选择今日任务档位
+
+**用途**:用户选择今日要做的档位(A/B/C/D/E),选后不可改,只能升级到更高档。建议前端做二次确认弹窗。
+
+| 项目 | 说明 |
+|------|------|
+| 路径 | `/superball/select-tier` |
+| 方法 | GET / POST |
+| 请求参数 | tier(必填) |
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| tier | string | 是 | 档位:A / B / C / D / E(大小写均可) |
+
+**成功时 `data`:**
+
+```json
+{ "message": "Tier selected" }
+```
+
+**常见失败:** 今日已选过档、档位不合法等,`msg` 会返回错误说明。
+
+---
+
+### 2.3 升级今日任务档位
+
+**用途**:今日任务已完成且当前不是 A 档时,可升级到更高档(如 D→C),进度保留,需满足新档的充值和流水要求。
+
+| 项目 | 说明 |
+|------|------|
+| 路径 | `/superball/upgrade-tier` |
+| 方法 | GET / POST |
+| 请求参数 | tier(必填) |
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| tier | string | 是 | 目标档位:A / B / C / D / E |
+
+**成功时 `data`:**
+
+```json
+{ "message": "Tier upgraded" }
+```
+
+**常见失败:** 未完成任务、目标档不高于当前档、进度不足等。
+
+---
+
+### 2.4 领取今日任务奖励(领球)
+
+**用途**:今日任务(充值+流水)达标且未领过时,调用后获得对应档位的「球」,之后需去选号。会更新奖池统计和用户系数。
+
+| 项目 | 说明 |
+|------|------|
+| 路径 | `/superball/claim-reward` |
+| 方法 | GET / POST |
+| 请求参数 | 无 |
+
+**成功时 `data`:**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| ball_count | number | 本次获得的球数 |
+| message | string | 提示文案(如:领取成功,请为球选号) |
+
+**常见失败:** 未完成任务、今日已领过等。
+
+---
+
+### 2.5 提交球的选号
+
+**用途**:用户为今日领到的每个球选择号码(0-9),可重复。全部选完并确认后调用,提交后不可改。
+
+| 项目 | 说明 |
+|------|------|
+| 路径 | `/superball/submit-numbers` |
+| 方法 | GET / POST |
+| 请求参数 | numbers(必填) |
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| numbers | array | 是 | 每个球的号码,顺序对应球序号,如 `[3, 7, 3, 9, 0]`,长度须等于今日获得的球数 |
+
+**成功时 `data`:**
+
+```json
+{ "message": "Numbers submitted" }
+```
+
+**常见失败:** 数量与球数不一致、今日已提交过、号码非 0-9 等。
+
+---
+
+### 2.6 查询我的球及选号
+
+**用途**:选号页或结果页查看某天「我的球」及已选号码。
+
+| 项目 | 说明 |
+|------|------|
+| 路径 | `/superball/my-balls` |
+| 方法 | GET / POST |
+| 请求参数 | date(可选) |
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| date | string | 否 | 日期,格式 `YYYY-MM-DD`,不传默认当天 |
+
+**成功时 `data`:**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| balls | array | 球列表,项:`{ ball_index, number }`,ball_index 从 1 开始 |
+
+---
+
+### 2.7 近 7 天幸运号码
+
+**用途**:展示最近 7 天的幸运号码(0-9),用于说明与展示。
+
+| 项目 | 说明 |
+|------|------|
+| 路径 | `/superball/lucky-numbers` |
+| 方法 | GET / POST |
+| 请求参数 | 无 |
+
+**成功时 `data`:**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| lucky_numbers | array | 项:`{ date, lucky_number }`,date 为 `YYYY-MM-DD` |
+
+---
+
+### 2.8 领取昨日奖金
+
+**用途**:次日用户主动领取「昨日」的奖金。昨日有选号球且未领过时,`info` 里 `yesterday.can_claim_yesterday` 为 true,并给出 `pending_prize_display`;用户点击「领取昨日奖金」后调此接口,金额入账,仅可领一次。
+
+| 项目 | 说明 |
+|------|------|
+| 路径 | `/superball/claim-yesterday-reward` |
+| 方法 | GET / POST |
+| 请求参数 | 无 |
+
+**成功时 `data`:**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| total_amount | number | 本次发放总金额(内部单位) |
+| total_amount_display | number | 本次发放总金额展示用 |
+| base_amount | number | 其中基础奖励部分(内部单位) |
+| lucky_amount | number | 其中幸运号奖励部分(内部单位) |
+| multiplier | number | 结算使用的系数 |
+
+**常见失败:** 昨日已领过、昨日无球可领、奖池未就绪等。
+
+---
+
+## 三、前端流程建议
+
+1. **进入活动页**  
+   调 `/superball/info`,用返回的 `yesterday`、`today`、`user_task`、`multiplier`、`lucky_numbers_7_days` 渲染页面。
+
+2. **今日未选档**  
+   展示档位列表(`tiers`),用户选择后调 `/superball/select-tier?tier=X`(建议二次确认)。
+
+3. **今日任务进行中**  
+   用 `user_task.recharge_progress/turnover_progress` 与 `recharge_required/turnover_required` 做进度条;若 `task_completed` 且 `can_upgrade`,可展示「升级档位」;若 `can_claim`,展示「领取奖励」按钮。
+
+4. **领取今日奖励(领球)**  
+   调 `/superball/claim-reward`,成功后根据返回的 `ball_count` 进入选号页。
+
+5. **选号页**  
+   可为每个球选 0-9,支持手动/随机,选满后调 `/superball/submit-numbers`,body 传 `numbers: [1,2,3,...]`。
+
+6. **昨日奖金**  
+   若 `yesterday.can_claim_yesterday === true`,展示「领取昨日奖金 ¥XX」(用 `pending_prize_display`);用户点击后调 `/superball/claim-yesterday-reward`。
+
+7. **金额展示**  
+   接口中带 `_display` 的字段为前端展示用金额;未带 display 的为内部单位,一般仅做兼容或特殊逻辑使用。
+
+---
+
+## 四、档位与规则速查
+
+| 档位 | 充值要求 | 流水要求 | 奖励球数 |
+|------|----------|----------|----------|
+| A | 500 | 2000 | 30 |
+| B | 200 | 1000 | 10 |
+| C | 100 | 500 | 5 |
+| D | 50 | 200 | 2 |
+| E | 29 | 100 | 1 |
+
+- 奖励系数:初始 1,连续完成 +0.5/天(上限 3),间隔 1 天未完成 -0.5(下限 1)。
+- 昨日奖金:次日由用户主动领取,不自动发放;金额 = 基础奖励 × 系数 + 幸运号奖励(每中一球 10 单位展示金额)。
+
+文档版本:基于当前后端实现整理,如有新增字段或约定以实际接口为准。

+ 107 - 0
app/Console/Commands/SuperballUpdatePoolAndStats.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Facade\TableName;
+use App\Services\SuperballActivityService;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * Superball 活跃活动:每分钟刷新奖池与展示统计(pool_amount、total_balls、completed_count)
+ *
+ * - pool_amount: 取当日游戏总流水的 1/10(来源:RecordGameRoomDayInfo.TotalBet)
+ * - total_balls & completed_count: 按时间段做一些随机增长,用于前端展示实时热度
+ */
+class SuperballUpdatePoolAndStats extends Command
+{
+    protected $signature = 'superball:update-pool-stats';
+
+    protected $description = 'Superball: update pool_amount (1/10 of total game turnover) and grow total_balls/completed_count every minute';
+
+    /** @var SuperballActivityService */
+    protected $service;
+
+    public function __construct(SuperballActivityService $service)
+    {
+        parent::__construct();
+        $this->service = $service;
+    }
+
+    public function handle(): int
+    {
+        $today = Carbon::today();
+        $dateStr = $today->format('Y-m-d');
+        $dateId = $today->format('Ymd');
+
+        try {
+            // 1. 计算当日总流水(TotalBet)并取 1/10 作为奖池
+            $roomRow = DB::table('QPRecordDB.dbo.RecordGameRoomDayInfo')
+                ->where('DateID', $dateId)
+                ->where('SortID', -1)
+                ->selectRaw('SUM(TotalBet) AS flowing_water_new')
+                ->first();
+
+            $flowing = $roomRow && isset($roomRow->flowing_water_new)
+                ? (int)$roomRow->flowing_water_new
+                : 0;
+
+            // 奖池 = 总流水的 1/10(内部单位),向下取整
+            $poolAmount = (int)floor($flowing / 10);
+
+            // 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();
+                }
+
+                // 3. 计算本次要增加的 completed_count 和 total_balls
+                $hour = (int)$today->format('G'); // 0-23
+
+                if ($hour < 1) {
+                    // 00:00 - 00:59
+                    $completedInc = mt_rand(3, 10);
+                    $multipliers = [1, 2, 3, 6];
+                } else {
+                    // 01:00 以后
+                    $completedInc = mt_rand(7, 20);
+                    $multipliers = [1, 2, 3, 6, 15];
+                }
+
+                $multiplier = $multipliers[array_rand($multipliers)];
+                $ballsInc = $completedInc * $multiplier;
+
+                DB::connection('write')->table(TableName::agent() . 'superball_daily')
+                    ->where('pool_date', $dateStr)
+                    ->update([
+                        'pool_amount' => $poolAmount,
+                        'completed_count' => (int)$daily->completed_count + $completedInc,
+                        'total_balls' => (int)$daily->total_balls + $ballsInc,
+                        'updated_at' => now()->format('Y-m-d H:i:s'),
+                    ]);
+            });
+
+            \Log::info("Superball pool stats updated for {$dateStr}, pool_amount={$poolAmount}");
+            return true;
+        } catch (\Throwable $e) {
+            \Log::error('Superball update pool stats failed: ' . $e->getMessage());
+            \Log::error('Superball update pool stats', [
+                'date' => $dateStr,
+                'error' => $e->getMessage(),
+            ]);
+            return false;
+        }
+    }
+}
+

+ 4 - 1
app/Console/Kernel.php

@@ -13,6 +13,7 @@ use App\Console\Commands\RecordServerGameCount;
 use App\Console\Commands\RecordServerGameCountYesterday;
 use App\Console\Commands\RecordThreeGameYesterday;
 use App\Console\Commands\RecordUserScoreChangeStatistics;
+use App\Console\Commands\SuperballUpdatePoolAndStats;
 use Illuminate\Console\Scheduling\Schedule;
 use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
 
@@ -34,7 +35,8 @@ class Kernel extends ConsoleKernel
         DecStock::class,
         OnlineReport::class,
         DbQueue::class,
-        RecordThreeGameYesterday::class
+        RecordThreeGameYesterday::class,
+        SuperballUpdatePoolAndStats::class,
     ];
 
     /**
@@ -52,6 +54,7 @@ class Kernel extends ConsoleKernel
         $schedule->command('record_server_game_count_yesterday')->cron('05 0 * * * ')->description('按天统计游戏人数--今日执行昨日');
         $schedule->command('RecordPlatformData')->cron('10 0 * * * ')->description('数据统计');
         $schedule->command('RecordUserScoreChangeStatistics')->cron('03 0 * * * ')->description('用户金额变化明细按天按用户汇总');
+        $schedule->command('superball:update-pool-stats')->everyMinute()->description('Superball 每分钟刷新奖池及展示统计');
         $schedule->command('online_report')->everyMinute()->description('每分钟统计曲线');
 
 //        $schedule->command('record_three_game_yesterday')->cron('05 0 * * * ')->description('按天统计游戏人数--今日执行昨日');

+ 184 - 0
app/Http/Controllers/Admin/SuperballController.php

@@ -0,0 +1,184 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Facade\TableName;
+use App\Http\helper\NumConfig;
+use App\Services\SuperballActivityService;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * Superball 活跃活动后台统计(仅查询,无导出)
+ * - 每日数据(含真实完成人数、真实球数)
+ * - 用户任务完成情况
+ * - 用户奖励领取情况
+ */
+class SuperballController extends BaseController
+{
+    /**
+     * 每日总体数据(含真实用户完成人数、真实球数)
+     * GET /admin/superball/daily
+     */
+    public function daily(Request $request)
+    {
+        // 不需要日期筛选,直接按日期倒序分页
+        $list = DB::table(TableName::agent() . 'superball_daily')
+            ->orderBy('pool_date', 'desc')
+            ->paginate(30);
+
+        $dates = $list->pluck('pool_date')->all();
+        $realCompleted = [];
+        $realBalls = [];
+        if (!empty($dates)) {
+            $realCompleted = DB::table(TableName::agent() . 'superball_user_task')
+                ->whereIn('task_date', $dates)
+                ->where('status', 1)
+                ->selectRaw('task_date, COUNT(DISTINCT user_id) as cnt')
+                ->groupBy('task_date')
+                ->pluck('cnt', 'task_date')
+                ->all();
+            $realBalls = DB::table(TableName::agent() . 'superball_user_balls')
+                ->whereIn('ball_date', $dates)
+                ->selectRaw('ball_date, COUNT(*) as cnt')
+                ->groupBy('ball_date')
+                ->pluck('cnt', 'ball_date')
+                ->all();
+        }
+
+        foreach ($list as $row) {
+            $row->real_user_completed_count = (int)($realCompleted[$row->pool_date] ?? 0);
+            $row->real_total_balls = (int)($realBalls[$row->pool_date] ?? 0);
+            $row->pool_amount_display = number_float($row->pool_amount / NumConfig::NUM_VALUE);
+            $totalBalls = (int)($row->total_balls ?? 0);
+            $row->base_reward_per_ball_display = $totalBalls > 0
+                ? number_float($row->pool_amount / NumConfig::NUM_VALUE / $totalBalls)
+                : '0.00';
+
+            // 用户中奖球总数:直接使用 superball_daily.lucky_count 字段
+            $row->winning_balls_count = (int)($row->lucky_count ?? 0);
+
+            // 幸运奖励合计 = 今日中奖球数量 * 每个中奖球奖励(注意是活动配置的中奖奖励,不是基础奖励)
+            $perLuckyDisplay = SuperballActivityService::LUCKY_REWARD_PER_BALL; // 已是展示单位
+            if ($row->winning_balls_count > 0 && $perLuckyDisplay > 0) {
+                $row->total_lucky_reward_display = number_format(
+                    $perLuckyDisplay * $row->winning_balls_count,
+                    2,
+                    '.',
+                    ''
+                );
+            } else {
+                $row->total_lucky_reward_display = '0.00';
+            }
+        }
+
+        return view('admin.superball.daily', compact('list'));
+    }
+
+    /**
+     * 用户任务完成情况(含用户系数、球号码与数量、预计奖励,按更新时间逆序)
+     * GET /admin/superball/user-tasks
+     */
+    public function userTasks(Request $request)
+    {
+        $date = $request->input('date');
+        // 前端输入的是 AccountsInfo 的 GameID,这里按 GameID 反查出 UserID 过滤
+        $userId = trim((string)$request->input('user_id', ''));
+        $tier = strtoupper(trim((string)$request->input('tier', '')));
+        $status = $request->input('status', '');
+
+        $query = DB::table(TableName::agent() . 'superball_user_task as t')
+            ->leftJoin('QPAccountsDB.dbo.AccountsInfo as a', 't.user_id', '=', 'a.UserID')
+            ->leftJoin(TableName::agent() . 'superball_user_multiplier as m', 't.user_id', '=', 'm.user_id')
+            ->leftJoin(TableName::agent() . 'superball_tier_config as c', 't.tier', '=', 'c.tier')
+            ->leftJoin(TableName::agent() . 'superball_daily as d', 't.task_date', '=', 'd.pool_date')
+            ->selectRaw('t.*, a.GameID as game_id, m.multiplier, c.ball_count as tier_ball_count, d.pool_amount, d.total_balls as daily_total_balls');
+
+        if ($date) {
+            $query->where('t.task_date', $date);
+        }
+        if ($userId !== '') {
+            $query->where('a.GameID', $userId);
+        }
+        if ($tier && in_array($tier, ['A', 'B', 'C', 'D', 'E'], true)) {
+            $query->where('t.tier', $tier);
+        }
+        // 仅当明确选择 未领球(0) 或 已领球(1) 时才按状态筛选,避免空值被当成 0
+        if ($status !== '' && $status !== null && in_array((string)$status, ['0', '1'], true)) {
+            $query->where('t.status', (int)$status);
+        }
+
+        $query->orderBy('t.updated_at', 'desc');
+        $list = $query->paginate(20);
+
+        $userIds = $list->pluck('user_id')->unique()->values()->all();
+        $ballsByKey = [];
+        $taskDates = $list->pluck('task_date')->unique()->values()->all();
+        if (!empty($userIds) && !empty($taskDates)) {
+            $balls = DB::table(TableName::agent() . 'superball_user_balls')
+                ->whereIn('user_id', $userIds)
+                ->whereIn('ball_date', $taskDates)
+                ->orderBy('user_id')->orderBy('ball_index')
+                ->get();
+            foreach ($balls as $b) {
+                $k = $b->user_id . '_' . $b->ball_date;
+                if (!isset($ballsByKey[$k])) {
+                    $ballsByKey[$k] = ['numbers' => [], 'count' => 0];
+                }
+                $ballsByKey[$k]['numbers'][] = (int)$b->number;
+                $ballsByKey[$k]['count']++;
+            }
+        }
+
+        foreach ($list as $row) {
+            $row->user_multiplier = $row->multiplier !== null ? (float)$row->multiplier : 1.0;
+            $k = $row->user_id . '_' . $row->task_date;
+            $ballInfo = $ballsByKey[$k] ?? ['numbers' => [], 'count' => 0];
+            $row->ball_numbers_text = implode(',', $ballInfo['numbers']);
+            $row->ball_count_actual = $ballInfo['count'];
+            $ballCount = $ballInfo['count'] > 0 ? $ballInfo['count'] : (int)($row->tier_ball_count ?? 0);
+            $dailyTotal = (int)($row->daily_total_balls ?? 0);
+            if ($dailyTotal > 0 && $row->pool_amount !== null) {
+                $basePerBall = (int)$row->pool_amount / $dailyTotal;
+                $row->estimated_reward_display = number_float(($basePerBall * $ballCount * $row->user_multiplier) / NumConfig::NUM_VALUE);
+            } else {
+                $row->estimated_reward_display = '0.00';
+            }
+        }
+
+        return view('admin.superball.user_tasks', compact('list', 'date', 'userId', 'tier', 'status'));
+    }
+
+    /**
+     * 用户奖励领取情况
+     * GET /admin/superball/prizes
+     */
+    public function prizes(Request $request)
+    {
+        $date = $request->input('date');
+        $userId = (int)$request->input('user_id', 0);
+
+        $query = DB::table(TableName::agent() . 'superball_prize_log as p')
+            ->leftJoin('QPAccountsDB.dbo.AccountsInfo as a', 'p.user_id', '=', 'a.UserID')
+            ->selectRaw('p.*, a.GameID, a.NickName')
+            ->orderBy('p.settle_date', 'desc')
+            ->orderBy('p.user_id');
+
+        if ($date) {
+            $query->where('p.settle_date', $date);
+        }
+        if ($userId > 0) {
+            $query->where('p.user_id', $userId);
+        }
+
+        $list = $query->paginate(50);
+
+        foreach ($list as $row) {
+            $row->total_amount_display = number_float($row->total_amount / NumConfig::NUM_VALUE);
+            $row->base_amount_display = number_float($row->base_amount / NumConfig::NUM_VALUE);
+            $row->lucky_amount_display = number_float($row->lucky_amount / NumConfig::NUM_VALUE);
+        }
+
+        return view('admin.superball.prizes', compact('list', 'date', 'userId'));
+    }
+}

+ 1 - 0
app/Http/Controllers/Game/PayRechargeController.php

@@ -337,6 +337,7 @@ class PayRechargeController extends Controller
                 'bonus_day' => $bonusDay,
                 'start_day' => $startDay,
                 'bonus' => $dayRewardsData['bonus'] ?? [],
+                'daysPassed' => $daysPassed,
                 'claimed_days' => $claimedDays,
                 'progress' => $claimedDays . '/' . $bonusDay,
                 'status' => $dayRewardStatus,  // 0=不可领取, 1=可领取, 2=已领取, 3=过期

+ 180 - 0
app/Http/Controllers/Game/SuperballActivityController.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace App\Http\Controllers\Game;
+
+use App\Http\Controllers\Controller;
+use App\Services\SuperballActivityService;
+use Illuminate\Http\Request;
+
+/**
+ * Superball Activity API: recharge + turnover task, balls, lucky number, prize.
+ * File encoding: UTF-8.
+ */
+class SuperballActivityController extends Controller
+{
+    protected SuperballActivityService $service;
+
+    public function __construct(SuperballActivityService $service)
+    {
+        $this->service = $service;
+    }
+
+    /**
+     * Get full activity info: yesterday data, today data, tiers, user task, multiplier.
+     */
+    public function info(Request $request)
+    {
+        $user = $request->user();
+        $userId = $user ? (int) $user->UserID : 0;
+
+        if ($userId <= 0) {
+            return apiReturnFail(['web.activity.login_required', 'Please login first']);
+        }
+
+        try {
+            $data = $this->service->getInfo($userId);
+            return apiReturnSuc($data);
+        } catch (\Throwable $e) {
+            \Log::error('Superball info error', ['user_id' => $userId, 'error' => $e->getMessage()]);
+            return apiReturnFail(['web.activity.error', $e->getMessage()]);
+        }
+    }
+
+    /**
+     * Select task tier for today (A-E). Requires confirm on front.
+     */
+    public function selectTier(Request $request)
+    {
+        $user = $request->user();
+        if (!$user) {
+            return apiReturnFail(['web.activity.login_required', 'Please login first']);
+        }
+        $userId = (int) $user->UserID;
+        $tier = strtoupper(trim((string) $request->input('tier', '')));
+        if (!in_array($tier, ['A', 'B', 'C', 'D', 'E'], true)) {
+            return apiReturnFail(['web.superball.invalid_tier', 'Invalid tier']);
+        }
+
+        $result = $this->service->selectTier($userId, $tier);
+        if (isset($result['success']) && $result['success'] === false) {
+            return apiReturnFail($result['message']);
+        }
+        return apiReturnSuc(['message' => 'Tier selected']);
+    }
+
+    /**
+     * Upgrade to higher tier (keep progress). Only when task completed and not A.
+     */
+    public function upgradeTier(Request $request)
+    {
+        $user = $request->user();
+        if (!$user) {
+            return apiReturnFail(['web.activity.login_required', 'Please login first']);
+        }
+        $userId = (int) $user->UserID;
+        $newTier = strtoupper(trim((string) $request->input('tier', '')));
+        if (!in_array($newTier, ['A', 'B', 'C', 'D', 'E'], true)) {
+            return apiReturnFail(['web.superball.invalid_tier', 'Invalid tier']);
+        }
+
+        $result = $this->service->upgradeTier($userId, $newTier);
+        //dd($result,isset($result['success']) && $result['success'] === false,$result['success'] === false);
+        if (isset($result['success']) && $result['success'] === false) {
+            return apiReturnFail($result['message']);
+        }
+        return apiReturnSuc(['message' => 'Tier upgraded']);
+    }
+
+    /**
+     * Claim reward: grant balls, update daily, multiplier, mark task claimed.
+     */
+    public function claimReward(Request $request)
+    {
+        $user = $request->user();
+        if (!$user) {
+            return apiReturnFail(['web.activity.login_required', 'Please login first']);
+        }
+        $userId = (int) $user->UserID;
+
+        $result = $this->service->claimReward($userId);
+        if (isset($result['success']) && $result['success'] === false) {
+            return apiReturnFail($result['message']);
+        }
+        return apiReturnSuc($result);
+    }
+
+    /**
+     * Submit numbers for all balls (0-9 per ball). Array of numbers.
+     */
+    public function submitNumbers(Request $request)
+    {
+        $user = $request->user();
+        if (!$user) {
+            return apiReturnFail(['web.activity.login_required', 'Please login first']);
+        }
+        $userId = (int) $user->UserID;
+        $numbers = $request->input('numbers', []);
+        if (!is_array($numbers)) {
+            $numbers = [];
+        }
+
+        $result = $this->service->submitNumbers($userId, $numbers);
+        if (isset($result['success']) && $result['success'] === false) {
+            return apiReturnFail($result['message']);
+        }
+        return apiReturnSuc(['message' => 'Numbers submitted']);
+    }
+
+    /**
+     * Get my balls for a date (for number selection page or display). Default today.
+     */
+    public function getMyBalls(Request $request)
+    {
+        $user = $request->user();
+        if (!$user) {
+            return apiReturnFail(['web.activity.login_required', 'Please login first']);
+        }
+        $userId = (int) $user->UserID;
+        $date = $request->input('date', date('Y-m-d'));
+
+        try {
+            $balls = $this->service->getMyBalls($userId, $date);
+            return apiReturnSuc(['balls' => $balls]);
+        } catch (\Throwable $e) {
+            \Log::error('Superball getMyBalls error', ['user_id' => $userId, 'error' => $e->getMessage()]);
+            return apiReturnFail(['web.superball.error', $e->getMessage()]);
+        }
+    }
+
+    /**
+     * Get last 7 days lucky numbers (for display).
+     */
+    public function luckyNumbers(Request $request)
+    {
+        try {
+            $list = $this->service->getLast7DaysLuckyNumbers();
+            return apiReturnSuc(['lucky_numbers' => $list]);
+        } catch (\Throwable $e) {
+            \Log::error('Superball luckyNumbers error', ['error' => $e->getMessage()]);
+            return apiReturnFail(['web.superball.error', $e->getMessage()]);
+        }
+    }
+
+    /**
+     * Claim yesterday's reward (user claims next day, no auto distribution).
+     */
+    public function claimYesterdayReward(Request $request)
+    {
+        $user = $request->user();
+        if (!$user) {
+            return apiReturnFail(['web.activity.login_required', 'Please login first']);
+        }
+        $userId = (int) $user->UserID;
+
+        $result = $this->service->claimYesterdayReward($userId);
+        if (isset($result['success']) && $result['success'] === false) {
+            return apiReturnFail($result['message']);
+        }
+        return apiReturnSuc($result);
+    }
+}

+ 3 - 1
app/Http/Controllers/Game/WebRouteController.php

@@ -221,7 +221,9 @@ class WebRouteController extends Controller
             'registerOpen'=>$config->RegOpen??env('CONFIG_REG_OPEN','sms,mail'),//id,phone,sms,mail,guest
             'loginOpen'=>$config->LoginOpen??'id,phone,sms,mail,guest',
             'slotsPartner' => $slotsPartner,
-            'outLimit' => ['cashapp' => 2000,'paypal' => 800]
+            'outLimit' => ['cashapp' => 2000,'paypal' => 800],
+            'withdrawChannel' => ['cashapp','paypal'],
+            'freeChannel' => ['paypal'],
         ];
 
 

+ 643 - 0
app/Services/SuperballActivityService.php

@@ -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'),
+            ]
+        );
+    }
+}

+ 68 - 0
resources/views/admin/superball/daily.blade.php

@@ -0,0 +1,68 @@
+@extends('base.base')
+@section('base')
+    <div class="main-panel">
+        <div class="content-wrapper">
+            <div class="page-header">
+                <h3 class="page-title">
+                    <span class="page-title-icon bg-gradient-primary text-white mr-2">
+                        <i class="mdi mdi-calendar-multiple"></i>
+                    </span>
+                    Superball 每日数据
+                </h3>
+                <nav aria-label="breadcrumb">
+                    <ol class="breadcrumb">
+                        <li class="breadcrumb-item"><a href="#">活动管理</a></li>
+                        <li class="breadcrumb-item active" aria-current="page">Superball 每日数据</li>
+                    </ol>
+                </nav>
+            </div>
+            <div class="row">
+                <div class="col-lg-12 grid-margin stretch-card">
+                    <div class="card">
+                        <div class="card-body">
+                            <h4 class="card-title">每日数据列表</h4>
+                            <table class="table table-bordered">
+                                <thead>
+                                <tr>
+                                    <th>日期</th>
+                                    <th>奖池(展示)</th>
+                                    <th>总球数(展示)</th>
+                                    <th>完成人数(展示)</th>
+                                    <th>幸运号码</th>
+                                    <th>每个球的奖励(展示)</th>
+                                    <th>真实完成人数</th>
+                                    <th>真实球数</th>
+                                    <th>用户中奖球总数</th>
+                                    <th>幸运奖励合计(展示)</th>
+                                    <th>更新时间</th>
+                                </tr>
+                                </thead>
+                                <tbody>
+                                @foreach($list as $v)
+                                    <tr>
+                                        <td>{{ $v->pool_date }}</td>
+                                        <td>{{ $v->pool_amount_display ?? number_float($v->pool_amount / 100) }}</td>
+                                        <td>{{ $v->total_balls }}</td>
+                                        <td>{{ $v->completed_count }}</td>
+                                        <td>{{ $v->lucky_number }}</td>
+                                        <td>{{ $v->base_reward_per_ball_display ?? '0.00' }}</td>
+                                        <td>{{ $v->real_user_completed_count ?? 0 }}</td>
+                                        <td>{{ $v->real_total_balls ?? 0 }}</td>
+                                        <td>{{ $v->winning_balls_count ?? 0 }}</td>
+                                        <td>{{ $v->total_lucky_reward_display ?? '0.00' }}</td>
+                                        <td>{{ $v->updated_at }}</td>
+                                    </tr>
+                                @endforeach
+                                </tbody>
+                            </table>
+                            <div class="box-footer clearfix">
+                                共 <b>{{ $list->total() }}</b> 条,共 <b>{{ $list->lastPage() }}</b> 页
+                                {!! $list->links() !!}
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+@endsection

+ 78 - 0
resources/views/admin/superball/prizes.blade.php

@@ -0,0 +1,78 @@
+@extends('base.base')
+@section('base')
+    <div class="main-panel">
+        <div class="content-wrapper">
+            <div class="page-header">
+                <h3 class="page-title">
+                    <span class="page-title-icon bg-gradient-primary text-white mr-2">
+                        <i class="mdi mdi-gift"></i>
+                    </span>
+                    Superball 奖励记录
+                </h3>
+                <nav aria-label="breadcrumb">
+                    <ol class="breadcrumb">
+                        <li class="breadcrumb-item"><a href="#">活动管理</a></li>
+                        <li class="breadcrumb-item active" aria-current="page">Superball 奖励记录</li>
+                    </ol>
+                </nav>
+            </div>
+            <div class="row">
+                <div class="col-lg-12 grid-margin stretch-card">
+                    <div class="card">
+                        <div class="card-body">
+                            <h4 class="card-title">用户奖励领取情况</h4>
+                            <form class="well form-inline margin-top-20" method="get" action="/admin/superball/prizes">
+                                <div>
+                                    <span style="padding-left: 10px">结算日期:</span>
+                                    <input type="date" name="date" class="form-control" value="{{ $date ?? '' }}" />
+                                    <span style="padding-left: 10px">用户ID:</span>
+                                    <input type="text" name="user_id" class="form-control" style="width: 100px;" value="{{ $userId ?: '' }}" placeholder="UserID" />
+                                </div>
+                                <div style="margin-top: 10px;">
+                                    <input type="submit" class="btn btn-sm btn-gradient-dark btn-icon-text" value="查询"/>&nbsp;&nbsp;
+                                    <a href="/admin/superball/prizes" class="btn btn-sm btn-gradient-warning btn-icon-text">清空</a>
+                                </div>
+                            </form>
+                            <table class="table table-bordered">
+                                <thead>
+                                <tr>
+                                    <th>ID</th>
+                                    <th>用户ID</th>
+                                    <th>会员ID</th>
+                                    <th>昵称</th>
+                                    <th>结算日期</th>
+                                    <th>基础奖励(展示)</th>
+                                    <th>幸运奖励(展示)</th>
+                                    <th>系数</th>
+                                    <th>总奖励(展示)</th>
+                                    <th>领取时间</th>
+                                </tr>
+                                </thead>
+                                <tbody>
+                                @foreach($list as $v)
+                                    <tr>
+                                        <td>{{ $v->id }}</td>
+                                        <td>{{ $v->user_id }}</td>
+                                        <td>{{ $v->GameID ?? '-' }}</td>
+                                        <td>{{ $v->NickName ?? '-' }}</td>
+                                        <td>{{ $v->settle_date }}</td>
+                                        <td>{{ $v->base_amount_display ?? number_float($v->base_amount / 100) }}</td>
+                                        <td>{{ $v->lucky_amount_display ?? number_float($v->lucky_amount / 100) }}</td>
+                                        <td>{{ $v->multiplier }}</td>
+                                        <td>{{ $v->total_amount_display ?? number_float($v->total_amount / 100) }}</td>
+                                        <td>{{ $v->created_at }}</td>
+                                    </tr>
+                                @endforeach
+                                </tbody>
+                            </table>
+                            <div class="box-footer clearfix">
+                                共 <b>{{ $list->total() }}</b> 条,共 <b>{{ $list->lastPage() }}</b> 页
+                                {!! $list->appends(['date' => $date, 'user_id' => $userId])->links() !!}
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+@endsection

+ 95 - 0
resources/views/admin/superball/user_tasks.blade.php

@@ -0,0 +1,95 @@
+@extends('base.base')
+@section('base')
+    <div class="main-panel">
+        <div class="content-wrapper">
+            <div class="page-header">
+                <h3 class="page-title">
+                    <span class="page-title-icon bg-gradient-primary text-white mr-2">
+                        <i class="mdi mdi-account-check"></i>
+                    </span>
+                    Superball 用户任务
+                </h3>
+                <nav aria-label="breadcrumb">
+                    <ol class="breadcrumb">
+                        <li class="breadcrumb-item"><a href="#">活动管理</a></li>
+                        <li class="breadcrumb-item active" aria-current="page">Superball 用户任务</li>
+                    </ol>
+                </nav>
+            </div>
+            <div class="row">
+                <div class="col-lg-12 grid-margin stretch-card">
+                    <div class="card">
+                        <div class="card-body">
+                            <h4 class="card-title">用户任务完成情况</h4>
+                            <form class="well form-inline margin-top-20" method="get" action="/admin/superball/user-tasks">
+                                <div>
+                                    <span style="padding-left: 10px">任务日期:</span>
+                                    <input type="date" name="date" class="form-control" value="{{ $date ?? '' }}" />
+                                    <span style="padding-left: 10px">用户ID:</span>
+                                    <input type="text" name="user_id" class="form-control" style="width: 100px;" value="{{ $userId ?: '' }}" placeholder="UserID" />
+                                    <span style="padding-left: 10px">档位:</span>
+                                    <select name="tier" class="form-control" style="width: 80px;">
+                                        <option value="">全部</option>
+                                        <option value="A" @if(($tier ?? '') == 'A') selected @endif>A</option>
+                                        <option value="B" @if(($tier ?? '') == 'B') selected @endif>B</option>
+                                        <option value="C" @if(($tier ?? '') == 'C') selected @endif>C</option>
+                                        <option value="D" @if(($tier ?? '') == 'D') selected @endif>D</option>
+                                        <option value="E" @if(($tier ?? '') == 'E') selected @endif>E</option>
+                                    </select>
+                                    <span style="padding-left: 10px">状态:</span>
+                                    <select name="status" class="form-control" style="width: 100px;">
+                                        <option value="" @if((string)($status ?? '') === '') selected @endif>全部</option>
+                                        <option value="0" @if((string)($status ?? '') === '0') selected @endif>未领球</option>
+                                        <option value="1" @if((string)($status ?? '') === '1') selected @endif>已领球</option>
+                                    </select>
+                                </div>
+                                <div style="margin-top: 10px;">
+                                    <input type="submit" class="btn btn-sm btn-gradient-dark btn-icon-text" value="查询"/>&nbsp;&nbsp;
+                                    <a href="/admin/superball/user-tasks" class="btn btn-sm btn-gradient-warning btn-icon-text">清空</a>
+                                </div>
+                            </form>
+                            <table class="table table-bordered">
+                                <thead>
+                                <tr>
+                                    <th>ID</th>
+                                    <th>会员ID</th>
+                                    <th>任务日期</th>
+                                    <th>档位</th>
+                                    <th>状态</th>
+                                    <th>用户系数</th>
+                                    <th>球号码及数量</th>
+                                    <th>预计奖励(展示)</th>
+                                    <th>更新时间</th>
+                                </tr>
+                                </thead>
+                                <tbody>
+                                @foreach($list as $v)
+                                    <tr>
+                                        <td>{{ $v->id }}</td>
+                                        <td>
+                                            <a href="/admin/global/id_find?UserID={{ $v->user_id }}">
+                                                {{ $v->game_id ?? $v->user_id }}
+                                            </a>
+                                        </td>
+                                        <td>{{ $v->task_date }}</td>
+                                        <td>{{ $v->tier }}</td>
+                                        <td>{{ $v->status == 1 ? '已领球' : '未领球' }}</td>
+                                        <td>{{ $v->user_multiplier ?? 1 }}</td>
+                                        <td>{{ $v->ball_numbers_text !== '' ? $v->ball_numbers_text . '(' . $v->ball_count_actual . '个)' : '-' }}</td>
+                                        <td>{{ $v->estimated_reward_display ?? '0.00' }}</td>
+                                        <td>{{ $v->updated_at }}</td>
+                                    </tr>
+                                @endforeach
+                                </tbody>
+                            </table>
+                            <div class="box-footer clearfix">
+                                共 <b>{{ $list->total() }}</b> 条,共 <b>{{ $list->lastPage() }}</b> 页
+                                {!! $list->appends(['date' => $date, 'user_id' => $userId, 'tier' => $tier, 'status' => $status])->links() !!}
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+@endsection

+ 10 - 0
routes/game.php

@@ -302,6 +302,16 @@ Route::group([
     $route->any('/vip-withdraw/tasks', 'Game\ActivityController@getVipWithdrawTasks');
     $route->any('/vip-withdraw/claim', 'Game\ActivityController@claimVipWithdrawReward');
 
+    // Superball 活跃活动
+    $route->any('/superball/info', 'Game\SuperballActivityController@info');
+    $route->any('/superball/select-tier', 'Game\SuperballActivityController@selectTier');
+    $route->any('/superball/upgrade-tier', 'Game\SuperballActivityController@upgradeTier');
+    $route->any('/superball/claim-reward', 'Game\SuperballActivityController@claimReward');
+    $route->any('/superball/submit-numbers', 'Game\SuperballActivityController@submitNumbers');
+    $route->any('/superball/my-balls', 'Game\SuperballActivityController@getMyBalls');
+    $route->any('/superball/lucky-numbers', 'Game\SuperballActivityController@luckyNumbers');
+    $route->any('/superball/claim-yesterday-reward', 'Game\SuperballActivityController@claimYesterdayReward');
+
     $route->any('/mail/check', 'Game\MailController@newMsg');
     $route->any('/mail/list', 'Game\MailController@mailList');
     $route->any('/mail/del', 'Game\MailController@delete');

+ 5 - 0
routes/web.php

@@ -192,6 +192,11 @@ Route::group([
         $route->post('/treasure_goods/add', 'Admin\ExchangeController@treasureGoodsAdd');
         $route->get('/treasure_goods/update/{id}', 'Admin\ExchangeController@treasureGoodsUpdateView');
         $route->post('/treasure_goods/update/{id}', 'Admin\ExchangeController@treasureGoodsUpdate');
+
+        // Superball 活跃活动后台统计
+        $route->get('/superball/daily', 'Admin\SuperballController@daily')->defaults('name', 'Superball 每日数据');
+        $route->get('/superball/user-tasks', 'Admin\SuperballController@userTasks')->defaults('name', 'Superball 用户任务');
+        $route->get('/superball/prizes', 'Admin\SuperballController@prizes')->defaults('name', 'Superball 奖励记录');
         $route->get('/exchange/code_list', 'Admin\ExchangeController@exchangeCodeList');
         $route->get('/gift/add', 'Admin\ExchangeController@giftAddView');
         $route->get('/gift/list', 'Admin\ExchangeController@giftList');