Browse Source

不存在的数据库

doge 3 days ago
parent
commit
d37ed6d83c

+ 17 - 13
app/Services/PlayedGameService.php

@@ -14,18 +14,22 @@ class PlayedGameService
         if ($memberIds === []) return [];
 
         $played = [];
-        $third = DB::table('third_game_orders as o')->join('users as u', function ($join) {
-            $join->on('o.user_id', '=', 'u.id')->orWhere(function ($join) {
-                $join->whereNull('o.user_id')->on('o.member_id', '=', 'u.member_id');
-            });
-        })->whereIn('u.member_id', $memberIds)
-            ->where(function ($query) {
-                $query->whereNull('o.member_id')->orWhere('o.member_id', '')->orWhereColumn('o.member_id', 'u.member_id');
-            })
-            ->whereIn('o.game_type', range(1, 7))->whereIn('o.status', [0, 1])->where('o.bet_amount', '>', 0);
-        if ($start && $end) $third->whereBetween('o.bet_time', [$start, $end]);
-        foreach ($third->distinct()->get(['u.member_id', 'o.game_type']) as $row) {
-            $played[$row->member_id]['third:' . $row->game_type] = true;
+        $schema = DB::connection()->getSchemaBuilder();
+        // Game modules are optional; installations may not have every order table.
+        if ($schema->hasTable('third_game_orders')) {
+            $third = DB::table('third_game_orders as o')->join('users as u', function ($join) {
+                $join->on('o.user_id', '=', 'u.id')->orWhere(function ($join) {
+                    $join->whereNull('o.user_id')->on('o.member_id', '=', 'u.member_id');
+                });
+            })->whereIn('u.member_id', $memberIds)
+                ->where(function ($query) {
+                    $query->whereNull('o.member_id')->orWhere('o.member_id', '')->orWhereColumn('o.member_id', 'u.member_id');
+                })
+                ->whereIn('o.game_type', range(1, 7))->whereIn('o.status', [0, 1])->where('o.bet_amount', '>', 0);
+            if ($start && $end) $third->whereBetween('o.bet_time', [$start, $end]);
+            foreach ($third->distinct()->get(['u.member_id', 'o.game_type']) as $row) {
+                $played[$row->member_id]['third:' . $row->game_type] = true;
+            }
         }
 
         // sport_order.user_id stores the member ID, unlike third_game_orders.user_id (users.id).
@@ -38,8 +42,8 @@ class PlayedGameService
         ];
         $startTimestamp = $start ? Carbon::parse($start, ShareDefaults::TIMEZONE)->timestamp : null;
         $endTimestamp = $end ? Carbon::parse($end, ShareDefaults::TIMEZONE)->timestamp : null;
-        $schema = DB::connection()->getSchemaBuilder();
         foreach ($sources as [$table, $memberColumn, $timeColumn, $category, $statusColumn, $statuses, $timestamp]) {
+            if (!$schema->hasTable($table)) continue;
             $query = DB::table($table)->whereIn($memberColumn, $memberIds)
                 ->whereIn($statusColumn, $statuses)->where('amount', '>', 0);
             // Historical lottery/sports tables may not contain this optional per-order flag.

+ 2 - 0
docs/总后台/用户报表-游戏分类与提充差值.md

@@ -89,3 +89,5 @@ GET /admin/operation/exchangeList?page=1&limit=15&sort_field=withdraw_recharge_d
 本次不需要新增数据库字段或执行数据迁移。
 
 兼容历史表结构:`lhc_order`、`sport_order` 仅在存在 `is_faker` 字段时按该字段排除虚假订单。没有该字段时继续按真实会员、订单状态、投注金额及日期查询,无需补列;会员列表仍排除 `users.from=2` 的虚拟会员。
+
+游戏分类统计仅查询实际存在的游戏订单表。正式服缺少 `jisu_game_order` 或其他未启用游戏的订单表时,自动跳过该来源,其余游戏分类、充值、提现、盈亏与排序照常返回,无需为本次功能新建旧游戏订单表。

+ 49 - 0
tests/Integration/OperationExchangeListTest.php

@@ -3,6 +3,7 @@
 namespace Tests\Integration;
 
 use App\Http\Controllers\admin\Operation;
+use App\Services\PlayedGameService;
 use App\Services\Sharing\ShareDefaults;
 use Carbon\Carbon;
 use Illuminate\Config\Repository;
@@ -168,6 +169,54 @@ class OperationExchangeListTest extends TestCase
         $this->assertSame([$category], array_column($this->rows()['200']['played_games'], 'category'));
     }
 
+    public static function absentGameTables(): array
+    {
+        $cases = [];
+        foreach (['jisu_game_order', 'bets', 'sport_game_order', 'lhc_order', 'third_game_orders'] as $table) {
+            $cases[$table] = [$table];
+        }
+        return $cases;
+    }
+
+    #[DataProvider('absentGameTables')]
+    public function test_absent_optional_game_table_does_not_break_the_list_or_other_categories(string $table): void
+    {
+        $this->order('third_game_orders', extra: ['game_type' => 1]);
+        $this->order('sport_order');
+        $this->order('sport_game_order');
+        $this->order('lhc_order');
+        $this->order('bets');
+        $this->order('jisu_game_order');
+        DB::statement('DROP TABLE bot_' . $table);
+        $this->funds('充值', '100');
+        $this->funds('提现', '-25');
+        $this->funds('充值', '200', '200');
+
+        $rows = $this->rows(['start_time' => '2026-11-04', 'end_time' => '2026-11-04',
+            'sort_field' => 'withdraw_recharge_difference', 'sort_order' => 'desc']);
+        $this->assertSame(['200', '100'], array_column($rows, 'member_id'));
+        $expected = $table === 'third_game_orders' ? ['self:sport', 'self:lottery'] : ['third:1', 'self:sport', 'self:lottery'];
+        $this->assertSame($expected, array_column($rows['100']['played_games'], 'category'));
+        $this->assertSame('75.00', $rows['100']['withdraw_recharge_difference']);
+        $this->assertSame([], $rows['200']['played_games']);
+    }
+
+    public function test_absent_jisu_table_and_lottery_fake_flag_are_supported_together(): void
+    {
+        DB::statement('DROP TABLE bot_jisu_game_order');
+        DB::statement('ALTER TABLE bot_lhc_order DROP COLUMN is_faker');
+        $this->order('lhc_order');
+        $this->assertSame(['self:lottery'], array_column($this->rows()['100']['played_games'], 'category'));
+    }
+
+    public function test_no_game_tables_returns_no_played_categories(): void
+    {
+        foreach (['third_game_orders', 'sport_order', 'sport_game_order', 'lhc_order', 'bets', 'jisu_game_order'] as $table) {
+            DB::statement('DROP TABLE bot_' . $table);
+        }
+        $this->assertSame([], PlayedGameService::forMembers(['100']));
+    }
+
     public function test_third_party_identity_supports_user_id_only_and_does_not_mix_members(): void
     {
         $this->order('third_game_orders', extra: ['user_id' => 1, 'member_id' => null, 'game_type' => 1]);