Sfoglia il codice sorgente

Merge branch 'master' of http://47.76.126.2:3000/seven/bot-28

Your Name 1 settimana fa
parent
commit
872019c62c
75 ha cambiato i file con 4285 aggiunte e 78 eliminazioni
  1. 1 0
      .gitignore
  2. 30 20
      README.md
  3. 35 0
      app/Console/Commands/Leagues.php
  4. 79 0
      app/Console/Commands/OperationData.php
  5. 510 0
      app/Console/Commands/Sport.php
  6. 235 0
      app/Console/Commands/SportOdds.php
  7. 2 0
      app/Console/Kernel.php
  8. 11 2
      app/Helpers/helpers.php
  9. 12 0
      app/Http/Controllers/admin/ActivityReward.php
  10. 2 0
      app/Http/Controllers/admin/Balance.php
  11. 131 0
      app/Http/Controllers/admin/Banner.php
  12. 1 0
      app/Http/Controllers/admin/Config.php
  13. 103 0
      app/Http/Controllers/admin/Level.php
  14. 62 0
      app/Http/Controllers/admin/LhcLottery.php
  15. 49 0
      app/Http/Controllers/admin/LhcNumber.php
  16. 146 0
      app/Http/Controllers/admin/LhcOrder.php
  17. 154 0
      app/Http/Controllers/admin/Operation.php
  18. 167 0
      app/Http/Controllers/admin/Order.php
  19. 320 0
      app/Http/Controllers/admin/Sport.php
  20. 50 4
      app/Http/Controllers/admin/User.php
  21. 123 11
      app/Http/Controllers/admin/Wallet.php
  22. 110 0
      app/Http/Controllers/admin/YueBao.php
  23. 2 1
      app/Http/Controllers/api/ActivityReward.php
  24. 4 1
      app/Http/Controllers/api/BaseController.php
  25. 36 0
      app/Http/Controllers/api/Game.php
  26. 19 0
      app/Http/Controllers/api/Issue.php
  27. 31 2
      app/Http/Controllers/api/NewPc.php
  28. 118 0
      app/Http/Controllers/api/PcIssue.php
  29. 436 0
      app/Http/Controllers/api/Wallet.php
  30. 2 0
      app/Http/Kernel.php
  31. 62 0
      app/Http/Middleware/CheckToken.php
  32. 1 1
      app/Models/ActivityReward.php
  33. 1 1
      app/Models/BalanceLog.php
  34. 8 0
      app/Models/Banner.php
  35. 12 0
      app/Models/FundsRecord.php
  36. 10 0
      app/Models/Level.php
  37. 13 0
      app/Models/LhcLottery.php
  38. 9 0
      app/Models/LhcNumber.php
  39. 14 0
      app/Models/LhcOrder.php
  40. 30 0
      app/Models/Operation.php
  41. 42 0
      app/Models/Order.php
  42. 184 0
      app/Models/Sport.php
  43. 9 0
      app/Models/SportEvent.php
  44. 19 0
      app/Models/SportLeague.php
  45. 9 0
      app/Models/SportOdds.php
  46. 10 0
      app/Models/SportStatistics.php
  47. 19 0
      app/Models/SportTeam.php
  48. 32 5
      app/Models/User.php
  49. 25 0
      app/Models/UserLogin.php
  50. 13 0
      app/Models/UserSession.php
  51. 1 1
      app/Models/Wallet.php
  52. 23 0
      app/Models/WalletBonus.php
  53. 14 0
      app/Models/YuebaoItem.php
  54. 14 0
      app/Models/YuebaoLog.php
  55. 1 1
      app/Providers/RouteServiceProvider.php
  56. 4 0
      app/Services/ActivityRewardService.php
  57. 203 5
      app/Services/BalanceLogService.php
  58. 10 5
      app/Services/BetService.php
  59. 6 0
      app/Services/IssueService.php
  60. 9 0
      app/Services/Payment/QianBaoService.php
  61. 5 0
      app/Services/PaymentOrderService.php
  62. 7 3
      app/Services/PcIssueService.php
  63. 3 1
      app/Services/QianBaoWithdrawService.php
  64. 3 1
      app/Services/RebateService.php
  65. 98 0
      app/Services/SportClientService.php
  66. 10 1
      app/Services/UserService.php
  67. 4 4
      config/database.php
  68. 11 1
      example.env
  69. 0 0
      fixturesStatistics-2.json
  70. 1 0
      fixturesStatistics.json
  71. 88 3
      lang/en/messages.php
  72. 88 2
      lang/vi/messages.php
  73. 88 2
      lang/zh/messages.php
  74. 59 0
      routes/admin.php
  75. 32 0
      routes/api.php

+ 1 - 0
.gitignore

@@ -27,3 +27,4 @@ yarn-error.log
 /package-lock.json
 /src/
 /doc/
+DOCKER_README.md

+ 30 - 20
README.md

@@ -27,12 +27,12 @@ git clone http://47.76.126.2:3000/seven/bot-28.git
 
 PHP 需要安装扩展:`php_openssl`  `php_gd`  `php_curl`  `php_gmp`  `php_fileinfo`  `php_zip`  `php_pdo_mysql`
 
-|  所需环境 | 版本 | 备注 | 推荐版本 |
-|---------|----|----|---|
-| linux    | \>= 7.0 |     | 7.9 |
-| nginx    | \>= 1.17 |     | 最新的 |
-| php | \>= 8.0.2 |   8.0.2 ~ 8.2   | 8.2.9 |
-| mysql    | \>= 5.7 | 必须要5.7及以上     | 5.7 |
+|  所需环境 | 版本      | 备注            | 推荐版本 |
+|----------|-----------|----------------|----------|
+| linux    | \>= 7.0   |                | 7.9      |
+| nginx    | \>= 1.17  |                | 最新的    |
+| php      | \>= 8.0.2 |   8.0.2 ~ 8.2  | 8.2.9    |
+| mysql    | \>= 5.7   | 必须要5.7及以上  | 5.7     |
 | node     | >= 22 
 ### 安装依赖
 ```bash
@@ -72,7 +72,7 @@ php artisan storage:link
 location / {
         add_header 'Access-Control-Allow-Origin' '*';
         add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
-        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
+        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, Lang';
         add_header 'Access-Control-Allow-Credentials' 'true';
         add_header 'Access-Control-Max-Age' 3600;
 
@@ -101,6 +101,14 @@ APP_NAME=lovingtalk
 APP_DEBUG=false
 APP_URL=http://localhost:8080
 
+#足球接口域名
+FOOTBALL_APP_URL=https://api.8xbet001.com
+
+# 足球数据API平台的秘钥和地址
+API_FOOTBALL_KEY=b60ecb5e998be566e592068b3f6d98b1
+API_FOOTBALL_HOST=https://v3.football.api-sports.io
+
+
 # 数据库
 DB_CONNECTION=mysql
 DB_HOST=127.0.0.1
@@ -142,19 +150,28 @@ MAIL_EXP=600  # 邮件验证码有效期(秒)
 - .env设置机器人信息
 - 访问 https://域名/api/setWebHook
 
-## 五、定时任务每5秒执行
+## 五、执行脚本(守护进程)
 - 先创建队列表
     - php artisan queue:table
     - php artisan migrate
-- 修改.env配置 使用数据库做队列
-    QUEUE_CONNECTION=database
-- php artisan five_task 将本程序加到命令里
-- 使用supervisor 配置 php artisan queue:work --queue
+    
+    (1) 队列进程:php artisan queue:work --queue   #【此进程启动或重启后,都需手动执行一次命令:php artisan five_task】
 
-## 六、安装wkhtmltopdf网页图片
+
+## 六、执行脚本(添加计划任务)
+    (1) 检测超时未开始的足球赛事【每隔 5 分钟执行一次】:cd /www/wwwroot/bot-28 && php artisan sport 3
+    (2) 检测进行中3分钟未变更的足球赛事【每隔 1 分钟执行一次】:cd /www/wwwroot/bot-28 && php artisan sport 2
+    (3) 实时更新进行中的足球赛事【每隔 6 秒执行一次】: cd /www/wwwroot/bot-28 && php artisan sport 1
+    (4) 实时更新直播的赔率【每隔 6 秒执行一次】: cd /www/wwwroot/bot-28 && php artisan sport:odds 1
+    (5) 每三小时更新赔率【每隔 3 小时的第 5 分钟执行一次】: cd /www/wwwroot/bot-28 && php artisan sport:odds 0
+    (6) 每三小时拉取近7天的赛事【每隔 3 小时的第 0 分钟执行一次】: cd /www/wwwroot/bot-28 && php artisan sport 0
+
+    
+## 七、安装wkhtmltopdf网页图片
 - apt install wkhtmltopdf
 - yum install wkhtmltopdf
 
+
 ## 七、支付配置
 
 ## 八、计划任务
@@ -170,11 +187,4 @@ MAIL_EXP=600  # 邮件验证码有效期(秒)
     cd 开奖图片目录 && rm -rf ./*
 
 
-    
-
-
-
-
-
-
 

+ 35 - 0
app/Console/Commands/Leagues.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Services\SportClientService;
+
+class Leagues extends Command
+{
+    /**
+     * 命令名称和签名
+     *
+     * @var string
+     */
+    protected $signature = 'leagues';
+    protected $is_live = 0;
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '获取所有联赛信息';
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $data = SportClientService::leagues(['season' => date('Y')]);
+        file_put_contents("leagues.json", json_encode($data));
+    }
+
+}

+ 79 - 0
app/Console/Commands/OperationData.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Models\Operation;
+use App\Models\FundsRecord;
+use App\Models\Order;
+use App\Models\Wallet;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\DB;
+
+class OperationData extends Command
+{
+    /**
+     * 命令名称和签名
+     *
+     * @var string
+     */
+    protected $signature = 'operation:data';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '统计昨日的充值提现订单等数据(每天00:00 之后执行)';
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $this->info('开始执行统计充值提现订单等数据任务...');
+
+        $this->addYesterdayOperationData();
+        $this->info('结束执行统计充值提现订单等数据任务');
+    }
+
+    public function addYesterdayOperationData()
+    {
+        $yesterday = Carbon::yesterday()->format('Y-m-d');
+        $data = [
+            'date' => $yesterday,
+            'recharge' => 0,
+            'user_total_money' => 0,
+        ];
+        $data['recharge'] = FundsRecord::whereIn('change_type', ['充值','人工充值','三方充值'])
+            ->where('created_at', '>=', "{$yesterday} 00:00:00")
+            ->where('created_at', '<=', "{$yesterday} 23:59:59")
+            ->sum('amount');
+
+        $data['withdraw'] = FundsRecord::whereIn('change_type', ['提现','三方提现'])
+            ->where('created_at', '>=', "{$yesterday} 00:00:00")
+            ->where('created_at', '<=', "{$yesterday} 23:59:59")
+            ->sum('amount');
+
+        $data['balance_difference'] = $data['recharge'] - $data['withdraw'];
+        $data['total_price'] = Order::where('create_time', '>=', strtotime($yesterday.' 00:00:00'))
+            ->where('create_time', '<=',  strtotime($yesterday.' 23:59:59'))
+            ->where('status', 1)
+            ->where('pay_status', 1)
+            ->where('return_status', 0)
+            ->sum('amount');
+
+        $data['user_total_money'] = Wallet::query()->sum('available_balance');
+
+        $info = Operation::where('date', $data['date'])->first();
+        
+        if (!$info) {
+            DB::table('bot_operation')->insert($data);
+        } else {
+            Operation::where('date', $data['date'])->update($data);
+        }
+        return true;
+    }
+
+}

+ 510 - 0
app/Console/Commands/Sport.php

@@ -0,0 +1,510 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Models\Sport as SportModel;
+use App\Models\SportTeam;
+use App\Models\SportLeague;
+use App\Models\SportStatistics;
+use App\Services\SportClientService;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\DB;
+use App\Models\SportEvent;
+use App\Models\Config;
+
+class Sport extends Command
+{
+    /**
+     * 命令名称和签名
+     *
+     * @var string
+     */
+    protected $signature = 'sport {is_live=0}';
+    protected $is_live = 0;
+
+    protected  $short_status = [
+            'TBD' => 0,
+            'NS' => 0,
+            '1H' => 1,
+            'HT' => 1,
+            '2H' => 1,
+            'ET' => 1,
+            'BT' => 1,
+            'P' => 1,
+            'SUSP' => 1,
+            'INT' => 1,
+            'FT' => 2,
+            'AET' => 2,
+            'PEN' => 2,
+            'PST' => 3,
+            'CANC' => 4,
+            'ABD' => 4,
+            'AWD' => 4,
+            'WO' => 4,
+            'LIVE' => 1,
+        ];
+    protected $long_status = [
+        'Time To Be Defined' => 0,
+        'Not Started' => 0,
+        'First Half' => 1,
+        'First Half, Kick Off' => 1,
+        'Halftime' => 1,
+        'Second Half' => 1,
+        'Second Half, 2nd Half Started' => 1,
+        'Extra Time' => 1,
+        'Break Time' => 1,
+        'Penalty In Progress' => 1,
+        'Match Suspended' => 1,
+        'Match Interrupted' => 1,
+        'Match Finished' => 2,
+        'Match Finished' => 2,
+        'Match Finished' => 2,
+        'Match Postponed' => 3,
+        'Match Cancelled' => 4,
+        'Match Abandoned' => 4,
+        'Technical Loss' => 4,
+        'WalkOver' => 4,
+        'In Progress' => 1,
+    ];        
+    
+    protected $fixture_status = [
+        'First Half' => 1,
+        'First Half, Kick Off' => 1,
+        'Halftime' => 1,
+        'Second Half' => 1,
+        'Second Half, 2nd Half Started' => 1,
+        'Extra Time' => 1,
+        'Break Time' => 1,
+        'Penalty In Progress' => 1,
+    ];     
+   
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '当天会去更新明天的赛事(23:59:00执行一次)';
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        // $this->info('开始执行统计比赛数据任务...');
+        $this->is_live = $this->argument('is_live');
+        if ($this->is_live == 0) {
+            //未开始的赛事拉取
+            $this->fixtures();
+        } elseif ($this->is_live == 1) {
+            //进行中的赛事,定时更新
+            $this->liveFixtures();
+        } elseif ($this->is_live == 2){
+            $this->checkLiveFixtures();
+            //$this->updateOvertimeFixtures();
+        } elseif ($this->is_live == 3) {
+            $this->checkOvertimeFixtures();
+        } else {
+            $this->initOdds();
+        }
+    }
+
+    //到了比赛开始时间,但是状态还是未开始,检查比赛
+    public function checkOvertimeFixtures()
+    { 
+        //体育赛事结束前几(分钟)锁盘,90分钟结束
+        $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
+        
+        // 比赛开始后,状态还是未开始的数据检测
+        $ids = SportModel::where('state', 0)->where('game_time', '<=', time())->pluck('data_id')->toArray();
+        if ($ids) {
+            $chunks = array_chunk($ids, 10);
+            foreach ($chunks as $chunk) {
+                $ids = implode('-', $chunk);
+                $data = SportClientService::fixtures(['ids' => $ids]);
+                $this->updateOrCreateSport($data, $sport_locked, 1);
+            }
+        }
+        return true;
+    }
+
+    //进行中超过3分钟没有更新数据的赛事,检查比赛是否结束
+    public function checkLiveFixtures()
+    { 
+        //体育赛事结束前几(分钟)锁盘,90分钟结束
+        $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
+
+        //1. 统一锁盘时间(比赛开始前1分钟)
+        SportModel::where(['is_locked' => 0, 'is_roll' => 0])->where('game_time', '<=', time() + 90)->update(['is_locked' => 1]);
+
+        //2. 比赛进行中,超过3分钟未更新的数据检测
+        $end_time = date("Y-m-d H:i:s", time() - 180);
+        $ids = SportModel::where('state', 1)->where('updated_at', '<=', $end_time)->pluck('data_id')->toArray();
+        if ($ids) {
+            $chunks = array_chunk($ids, 10);
+            foreach ($chunks as $chunk) {
+                $ids = implode('-', $chunk);
+                $data = SportClientService::fixtures(['ids' => $ids]);
+                $this->updateOrCreateSport($data, $sport_locked, 1);
+            }
+        }
+        return true;
+    }
+    
+    //更新进行中的赛事
+    public function liveFixtures()
+    {
+        //体育赛事结束前几(分钟)锁盘,90分钟结束
+        $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
+        $data = SportClientService::fixtures(['live' => 'all']);
+        file_put_contents("fixturesLive.json", json_encode($data));
+        $this->updateOrCreateSport($data, $sport_locked);
+        return true;
+    }
+
+    private function updateOrCreateSport($data, $sport_locked, $is_check = 0) {
+        $data = $data['response'];
+        $tableData = [];
+        $status = $this->short_status;
+        foreach ($data as $item) {
+            $home_statistics = !empty($item['statistics']) ? $item['statistics'][0]['statistics'] : '';
+            $away_statistics = !empty($item['statistics']) ? $item['statistics'][1]['statistics'] : '';
+
+            $sport_data = [
+                'data_id' => $item['fixture']['id'],
+                'home_team_id' => $item['teams']['home']['id'],
+                'home_team_en' => $item['teams']['home']['name'],
+                'home_team_logo' => $item['teams']['home']['logo'],
+                'guest_team_id' => $item['teams']['away']['id'],
+                'guest_team_en' => $item['teams']['away']['name'],
+                'guest_team_logo' => $item['teams']['away']['logo'],
+                'half_score' => "{$item['score']['halftime']['home']}-{$item['score']['halftime']['away']}",
+                'rbt' => $item['fixture']['timestamp'],
+                'score' => isset($item['goals']) ? "{$item['goals']['home']}-{$item['goals']['away']}":'-',
+                'extra_score' => isset($item['score']['extratime']) ? "{$item['score']['extratime']['home']}-{$item['score']['extratime']['away']}" : "",//加时赛比分
+                'league_id' => $item['league']['id'],
+                'league_en' => $item['league']['name'],
+                'league_data' => json_encode($item['league']),
+                'state' => $status[$item['fixture']['status']['short']],//比赛状态:0未开始1进行中2已完场3延期4取消
+                'game_time' => $item['fixture']['timestamp'],
+                'updated_at' => date('Y-m-d H:i:s'),
+                'home_statistics' => json_encode($home_statistics),
+                'away_statistics' => json_encode($away_statistics),
+                'is_send' => 0,
+            ];
+            
+            
+            $info = SportModel::where('data_id', $item['fixture']['id'])->first();
+            
+            $fixture_status = null;
+            if (isset($item['fixture']['status']['long']) ) {
+                $long = $item['fixture']['status']['long'];
+                if (isset($this->fixture_status[$long])) {
+                    $fixture_status = json_encode($item['fixture']['status']);
+                    $sport_data['fixture_status'] = $fixture_status;
+                } 
+                if (isset($this->long_status[$long])) {
+                    $sport_data['state'] = $this->long_status[$long];
+                }
+            }
+            //提前锁盘(比赛进行时长,分钟)
+            if (isset($item['fixture']['status']['elapsed'])) {
+                $elapsed = $item['fixture']['status']['elapsed'];
+                if ((int)$elapsed >= 90 - $sport_locked ) {
+                   $sport_data['is_locked'] = 1;
+                }
+            }
+            
+            $sport_data['score'] = $sport_data['score'] == '-' ? '' : $sport_data['score'];
+            $sport_data['half_score'] = $sport_data['half_score'] == '-' ? '' : $sport_data['half_score'];
+            $sport_data['extra_score'] = $sport_data['extra_score'] == '-' ? '' : $sport_data['extra_score'];
+            
+            //锁盘
+            if (isset($item['fixture']['status']['blocked']) && $item['fixture']['status']['blocked']) {
+                $sport_data['is_locked'] = 1;
+            }
+            //已结束
+            if (isset($item['fixture']['status']['finished']) && $item['fixture']['status']['finished']) {
+                $sport_data['state'] = 2;
+            }
+
+            //如果赛事取消、延期等,标记需要退款
+            if ($sport_data['state'] > 2) {
+                $sport_data['refund_status'] = 1;
+            }
+            
+            if (!$info) {
+                $sport_data['created_at'] = date('Y-m-d H:i:s');
+                $sport_data['status'] = 1;
+                $sport_data['home_team'] = SportTeam::getTeamName($sport_data['home_team_id']);
+                $sport_data['guest_team'] = SportTeam::getTeamName($sport_data['guest_team_id']);
+                $sport_data['league'] = SportLeague::getLeagueName($sport_data['league_id']);
+                $tableData[] = $sport_data;
+            } else {
+                SportModel::where('data_id', $item['fixture']['id'])->update($sport_data);
+            }
+
+            if ($sport_data['state'] == 2) {
+                $data_id = $item['fixture']['id'];
+                //比赛结束,插入比赛事件
+                if (!empty($item['events'])) {
+                    $this->insertSportEvents($data_id, $item['events']);
+                }
+                //比赛结束,插入比赛统计数据
+                $data = SportClientService::statistics(['fixture' => $data_id,'half' => 'true']);
+                // file_put_contents("fixturesStatistics.json", json_encode($data));
+                $statistics_data = empty($data['response']) ? [] : $data['response'];
+                foreach($statistics_data as $item) {
+                    $team_id = $item['team']['id'];
+                    //上半场的数据
+                    if(!empty($item['statistics_1h'])) {
+                        $statistics_1h = $item['statistics_1h'];
+                        $this->insertSportStatistics($data_id, $team_id, $statistics_1h, 1); 
+                    }
+                    //下半场的数据
+                    if(!empty($item['statistics_2h'])) {
+                        $statistics_2h = $item['statistics_2h'];
+                        $this->insertSportStatistics($data_id, $team_id, $statistics_2h, 2); 
+                    }
+                }
+                
+            }
+        }
+        if ($tableData) {
+            SportModel::insert($tableData);
+        }
+        return true;
+    }
+    
+    /**
+     * 获取指定日期的所有赛事
+     *
+     * @return array
+     */
+    public function fixtures()
+    {
+        //根据配置拉取多少天的赛事信息
+        $days = Config::where('field', 'sport_days')->first()->val ?? 1;
+        for($i=0;$i<$days;$i++) {
+            $date = Carbon::today()->addDay($i)->toDateString();
+            $data = SportClientService::fixtures(['date' => $date]);
+    
+            $data = $data['response'];
+            $tableData = [];
+            $status = $this->short_status;
+            foreach ($data as $item) {
+                $home_statistics = !empty($item['statistics']) ? $item['statistics'][0]['statistics'] : '';
+                $away_statistics = !empty($item['statistics']) ? $item['statistics'][1]['statistics'] : '';
+    
+                $sport_data = [
+                    'data_id' => $item['fixture']['id'],
+                    'home_team_id' => $item['teams']['home']['id'],
+                    'home_team_en' => $item['teams']['home']['name'],
+                    'home_team_logo' => $item['teams']['home']['logo'],
+                    'guest_team_id' => $item['teams']['away']['id'],
+                    'guest_team_en' => $item['teams']['away']['name'],
+                    'guest_team_logo' => $item['teams']['away']['logo'],
+                    'half_score' => "{$item['score']['halftime']['home']}-{$item['score']['halftime']['away']}",
+                    'rbt' => $item['fixture']['timestamp'],
+                    'score' => isset($item['score']['fulltime']) ? "{$item['score']['fulltime']['home']}-{$item['score']['fulltime']['away']}":'-',
+                    'league_id' => $item['league']['id'],
+                    'league_en' => $item['league']['name'],
+                    'league_data' => json_encode($item['league']),
+                    // 'state' => $status[$item['fixture']['status']['short']],//比赛状态:0未开始1进行中2已完场3延期4取消
+                    'game_time' => $item['fixture']['timestamp'],
+                    'updated_at' => now(),
+                    'home_statistics' => $home_statistics,
+                    'away_statistics' => $away_statistics,
+                    'is_send' => 0,
+                ];
+                $status_short = strtoupper($item['fixture']['status']['short']);
+                if (isset($status[$status_short])) {
+                    $sport_data['state'] = $status[$status_short];
+                }
+                $sport_data['score'] = $sport_data['score'] == '-' ? '' : $sport_data['score'];
+                $sport_data['half_score'] = $sport_data['half_score'] == '-' ? '' : $sport_data['half_score'];
+    
+                if (!SportModel::where('data_id', $item['fixture']['id'])->exists()) {
+                    $sport_data['created_at'] = now();
+                    
+                    $sport_data['home_team'] = SportTeam::getTeamName($sport_data['home_team_id']);
+                    $sport_data['guest_team'] = SportTeam::getTeamName($sport_data['guest_team_id']);
+                    $sport_data['league'] = SportLeague::getLeagueName($sport_data['league_id']);
+                    $tableData[] = $sport_data;
+                } else {
+                    
+                    $sport_data['home_team'] = SportTeam::getTeamName($sport_data['home_team_id']);
+                    $sport_data['guest_team'] = SportTeam::getTeamName($sport_data['guest_team_id']);
+                    $sport_data['league'] = SportLeague::getLeagueName($sport_data['league_id']);
+                    SportModel::where('data_id', $item['fixture']['id'])->update($sport_data);
+                }
+
+                //更新或创建球队和联赛信息
+                SportModel::addSportTeam($sport_data);
+                SportModel::addSportLeague($item['league']);
+
+                //比赛结束,插入比赛事件
+                if (isset($sport_data['state']) && $sport_data['state'] == 2 ) {
+                    
+                    $data_id = $item['fixture']['id'];
+                    if (!empty($item['events'])) {
+                        $this->insertSportEvents($data_id, $item['events']);
+                    }
+                    
+                    //比赛结束,插入比赛事件
+                    $data = SportClientService::statistics(['fixture' => $data_id,'half' => 'true']);
+                    // file_put_contents("fixturesStatistics.json", json_encode($data));
+                    $statistics_data = empty($data['response']) ? [] : $data['response'];
+                    foreach($statistics_data as $item) {
+                        $team_id = $item['team']['id'];
+                        //上半场的数据
+                        if(!empty($item['statistics_1h'])) {
+                            $statistics_1h = $item['statistics_1h'];
+                            $this->insertSportStatistics($data_id, $team_id, $statistics_1h, 1); 
+                        }
+                        //下半场的数据
+                        if(!empty($item['statistics_2h'])) {
+                            $statistics_2h = $item['statistics_2h'];
+                            $this->insertSportStatistics($data_id, $team_id, $statistics_2h, 2); 
+                        }
+                    }
+                }
+            }
+            if ($tableData) {
+                SportModel::insert($tableData);
+            }
+        }
+        
+        return true;
+    }
+    public function initOdds(){
+        $page = 1;
+        $limit = 10;
+        while (true) { 
+            $list = SportModel::where('odds','<>', null)->forPage($page, $limit)->get()->toArray();
+            if (empty($list)) {
+                break;
+            }
+            echo $page.PHP_EOL;
+            foreach($list as $item) {
+                $odds = json_decode($item['odds'], true);
+                foreach($odds as $odd) {
+                    $odd_id = $odd['id'];
+                    $odd_name = $odd['name'];
+                    $info = DB::table('sport_odds')->where('odd_id',$odd_id)->where('odd_name_en',$odd_name)->first();
+                    if ($info && (!$info->odd_name || $odd_name != $info->odd_name_en)) {
+                        DB::table('sport_odds')->where('id', $info->id)->update([
+                            'odd_name_en' => $odd_name,
+                        ]);
+                    } elseif (!$info) {
+                        DB::table('sport_odds')->insert([
+                            'odd_id' => $odd_id,
+                            'odd_name_en' => $odd_name,
+                            'created_at' => date('Y-m-d H:i:s'),
+                            'updated_at' => date('Y-m-d H:i:s'),
+                        ]);
+                    }
+                }
+            }
+
+            $page++;
+        }
+    }
+
+    //比赛开始后,超过3个小时,并且更新时间超过10分钟,还是进行中的数据,更新成已完成
+    public function updateOvertimeFixtures()
+    { 
+        // 比赛开始后,状态还是未开始的数据检测
+        $end_time = date("Y-m-d H:i:s", time() - 600);
+        SportModel::where('status', 1)->where('state', 1)->where('game_time', '<=', time() - 60*60*3)->where('updated_at', '<=', $end_time)->update(['state' =>2]);
+        return true;
+    }
+
+    //插入比赛事件数据
+    public function insertSportEvents($data_id, $events)
+    { 
+        foreach($events as $event) {
+            SportEvent::create([
+                'data_id' => $data_id,
+                'type' => $event['type'],
+                'time_elapsed' => $event['time']['elapsed'],
+                'time' => json_encode($event['time']),
+                'detail' => $event['detail'],
+                'player' => $event['player'] ? json_encode($event['player']) : $event['player'],
+                'team_id' => $event['team']['id'],
+                'comments' => $event['comments'],
+                'assist' => $event['assist'] ? json_encode($event['assist']) : $event['assist'],
+            ]);
+        }
+    }
+
+    //插入比赛统计数据
+    public function insertSportStatistics($data_id, $team_id, $statistics, $half)
+    { 
+        $insertData = [];
+        foreach($statistics as $item) {
+            if (!empty($item['type'])) {
+                switch ($item['type']) {
+                    case 'Shots on Goal':
+                        $insertData['shots_on_goal'] = $item['value'];
+                        break;
+                    case 'Shots off Goal':
+                        $insertData['shots_off_goal'] = $item['value'];
+                        break;
+                    case 'Shots insidebox':
+                        $insertData['shots_insidebox'] = $item['value'];
+                        break;
+                    case 'Shots outsidebox':
+                        $insertData['shots_outsidebox'] = $item['value'];
+                        break;
+                    case 'Total Shots':
+                        $insertData['total_shots'] = $item['value'];
+                        break;
+                    case 'Blocked Shots':
+                        $insertData['blocked_shots'] = $item['value'];
+                        break;
+                    case 'Fouls':
+                        $insertData['fouls'] = $item['value'];
+                        break;
+                    case 'Corner Kicks':
+                        $insertData['corner_kicks'] = $item['value'];
+                        break;
+                    case 'Offsides':
+                        $insertData['offsides'] = $item['value'];
+                        break;
+                    case 'Ball Possession':
+                        $insertData['ball_possession'] = $item['value'];
+                        break;
+                    case 'Yellow Cards':
+                        $insertData['yellow_cards'] = $item['value'];
+                        break;
+                    case 'Red Cards':
+                        $insertData['red_cards'] = $item['value'];
+                        break;
+                    case 'Goalkeeper Saves':
+                        $insertData['goalkeeper_saves'] = $item['value'];
+                        break;
+                    case 'Total passes':
+                        $insertData['total_passes'] = $item['value'];
+                        break;
+                    case 'Passes accurate':
+                        $insertData['passes_accurate'] = $item['value'];
+                        break;
+                    case 'Passes %':
+                        $insertData['passes_percent'] = $item['value'];
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+        if ($insertData) {
+            $insertData['data_id'] = $data_id;
+            $insertData['team_id'] = $team_id;
+            $insertData['half'] = $half;
+            SportStatistics::create($insertData);
+        }
+    }
+}

+ 235 - 0
app/Console/Commands/SportOdds.php

@@ -0,0 +1,235 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Models\Sport as SportModel;
+use App\Models\SportOdds as SportOddsModel;
+use App\Services\SportClientService;
+use Illuminate\Support\Facades\Log;
+use App\Models\Config;
+use Illuminate\Support\Facades\Cache;
+use Throwable; // 使用 Throwable 可以捕获所有 PHP 7+ 的异常和错误
+
+class SportOdds extends Command
+{
+    /**
+     * 命令名称和签名
+     *
+     * @var string
+     */
+    protected $signature = 'sport:odds {is_live=0}';
+
+    protected $is_live = 0;
+    
+    protected $long_status = [
+        'Time To Be Defined' => 0,
+        'Not Started' => 0,
+        'First Half' => 1,
+        'First Half, Kick Off' => 1,
+        'Halftime' => 1,
+        'Second Half' => 1,
+        'Second Half, 2nd Half Started' => 1,
+        'Extra Time' => 1,
+        'Break Time' => 1,
+        'Penalty In Progress' => 1,
+        'Match Suspended' => 1,
+        'Match Interrupted' => 1,
+        'Match Finished' => 2,
+        'Match Finished' => 2,
+        'Match Finished' => 2,
+        'Match Postponed' => 3,
+        'Match Cancelled' => 4,
+        'Match Abandoned' => 4,
+        'Technical Loss' => 4,
+        'WalkOver' => 4,
+        'In Progress' => 1,
+    ];        
+    
+    protected $fixture_status = [
+        'First Half' => 1,
+        'First Half, Kick Off' => 1,
+        'Halftime' => 1,
+        'Second Half' => 1,
+        'Second Half, 2nd Half Started' => 1,
+        'Extra Time' => 1,
+        'Break Time' => 1,
+        'Penalty In Progress' => 1,
+    ];        
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '赔率(直播赔率5秒更新一次,塞前赔率3小时更新一次)';
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $this->is_live = $this->argument('is_live');            
+        $this->sportOddsData($this->is_live);
+    }
+
+    public function sportOddsData($is_live)
+    {
+        if ($is_live == 1) {
+            // 直播赔率通常是一次获取全量,但也需要对整体更新逻辑做保护
+            $data = SportClientService::oddsLive([]);
+            if ($data) {
+                // file_put_contents("oddsLive.json",json_encode($data));
+                $this->updateOddsLive($data);
+            }
+        } else {
+            $limit = 3000;
+            //普通球赛是开赛前购买,更新数据
+            $where['state'] = 0;  //比赛状态:0未开始1进行中2已完场3延期4取消
+            $list = SportModel::where($where)->where('odds',null)->limit($limit)->get()->toArray();
+
+            foreach ($list as $item) {
+                // --- 关键优化:在循环内部嵌套 try-catch ---
+                try {
+                    $data = SportClientService::odds([
+                        'fixture' => $item['data_id'],
+                    ]);
+                    $this->updateOdds($item['data_id'], $data);
+                } catch (Throwable $e) {
+                    // 记录具体哪条赛事出错了,但不抛出,让循环继续
+                    Log::error($item['data_id'].",赛前赔率更新失败: " . $e->getMessage());
+                    continue; 
+                }
+            }
+        }
+        
+        return true;
+    }
+
+    public function updateOddsLive($data)
+    {
+        if (empty($data['response'])) return;
+
+        //体育赛事结束前几(分钟)锁盘,90分钟结束
+        $sport_locked = Config::where('field', 'sport_locked')->first()->val ?? 1;
+        $responses = $data['response'];
+
+        foreach ($responses as $item) {
+            // --- 关键优化:直播赔率逐条更新也要包裹,防止单条数据格式问题挂掉全盘 ---
+            try {
+                $data_id = $item['fixture']['id'];
+                $sport_info = SportModel::where('data_id',$data_id)->select('odds','odd_ids_locked')->first();
+                
+                if (!$sport_info) continue;
+
+                $old_odd_ids_locked = $sport_info['odd_ids_locked'];
+                $odds_data = $this->doOdds($item['odds'], $sport_info['odds']);
+                $odds = $odds_data['odds'];
+                $odds = !empty($odds) ? json_encode($odds) : null;
+                $odd_ids_locked = array_merge($old_odd_ids_locked, $odds_data['odd_ids_locked']);
+                
+                $update_data = [
+                    'is_send' => 0,
+                    'is_roll' => 1,
+                    'is_locked' => 0,
+                    'odd_ids_locked' => array_unique($odd_ids_locked),
+                    'odds' => $odds,
+                    'error' => 0, //异常类型:0正常;1直播赔率的赛事时异常
+                ];
+                $odd_fixture_status = null;//直播赔率中的赛事时间和状态(与直播赛事信息中的赛事时间和状态存在较大差异)
+                if (isset($item['fixture']['status']['long']) ) {
+                    // $long = $item['fixture']['status']['long'];
+                    // if (isset($this->fixture_status[$long])) {
+                        $sport_info = SportModel::where('data_id',$data_id)->first();
+                        $fixture_status = $sport_info['fixture_status'] ? json_decode($sport_info['fixture_status'],true) : [];
+                        
+                        $odd_fixture_status = $item['fixture']['status'];
+                        $update_data['odd_fixture_status'] = json_encode($odd_fixture_status);
+                        
+                        //如果时间差距超过3分钟,则锁盘(赛事时间90分钟内)
+                        if (isset($fixture_status['elapsed']) && $fixture_status['elapsed'] < 90 && isset($odd_fixture_status['elapsed'])) {
+                            if (abs($fixture_status['elapsed'] - $odd_fixture_status['elapsed']) >= 3) {
+                                
+                                $update_data['error'] = 1;      //异常
+                                $update_data['is_locked'] = 1; //锁盘
+                                unset($update_data['odds']);//不更新赔率
+                            } else {
+                                $update_data['error'] = 0;
+                            }
+                        }
+                    // } 
+                }
+                
+                //锁盘
+                if (isset($item['fixture']['status']['blocked']) && $item['fixture']['status']['blocked']) {
+                    $update_data['is_locked'] = 1;
+                }
+                //提前锁盘(比赛进行时长,分钟)
+                if (isset($item['fixture']['status']['elapsed'])) {
+                    $elapsed = $item['fixture']['status']['elapsed'];
+                    if ((int)$elapsed >= 90 - $sport_locked ) {
+                       $update_data['is_locked'] = 1;
+                    }
+                }
+                //已结束
+                if (isset($item['fixture']['status']['finished']) && $item['fixture']['status']['finished']) {
+                    $update_data['state'] = 2;
+                }
+                //如果赛事取消、延期等,标记需要退款
+                if (isset($update_data['state']) && $update_data['state'] > 2) {
+                    $update_data['refund_status'] = 1;
+                }
+
+                SportModel::where('data_id', $data_id)->update($update_data);
+
+            } catch (Throwable $e) {
+                Log::error("直播赔率单条更新失败 [ID: {$data_id}]: " . $e->getMessage());
+                continue;
+            }
+        }
+    }
+
+    public function updateOdds($data_id, $data)
+    {
+        if (!empty($data['response'][0]['bookmakers'][0]['bets'])) {
+            $odds = $data['response'][0]['bookmakers'][0]['bets'];
+            SportModel::where('data_id',$data_id)->update(['odds' => json_encode($odds), 'is_send' => 0]);
+        } 
+    }
+
+    //去掉无效的赔率
+    private function doOdds($odds, $old_odds) {
+        // 1. 获取赔率,缓存数据
+        $sport_odds = cache('sport_odds');
+        if (!$sport_odds) {
+            $sport_odds = SportOddsModel::where('function_name', '<>', null)->get()->toArray();
+            Cache::set('sport_odds', $sport_odds, 300); //有效期5分钟
+        }
+        $old_odds = $old_odds ? json_decode($old_odds, true) : [];
+        $odds_ids = []; //新的玩法id集合
+
+        $sport_odds = array_column($sport_odds, null,'odd_name_en');
+        $new_odds = [];
+        foreach($odds as $item) {
+            if (!isset($sport_odds[$item['name']])) {
+                continue;
+            }
+            $new_odds[] = $item;
+            $odds_ids[] = $item['id'];
+        }
+        $odd_ids_locked = [];
+        foreach($old_odds as $item) {
+            if (!in_array($item['id'], $odds_ids)) {
+                $new_odds[] = $item;
+                //历史玩法如果没有了,数据保存,加锁
+                $odd_ids_locked[] = $item['id'];
+            }
+        }
+        return [
+            'odds' => $new_odds,
+            'odd_ids_locked' => $odd_ids_locked,
+        ];
+    }
+
+}

+ 2 - 0
app/Console/Kernel.php

@@ -24,6 +24,8 @@ class Kernel extends ConsoleKernel
         //      ->withoutOverlapping() // 防止重复执行
         //      ->name('five-second-task') // 给任务起个名字
         //      ->onOneServer();       // 如果在多服务器环境
+
+        $schedule->command('sport')->dailyAt('23:59:00');
     }
 
     /**

+ 11 - 2
app/Helpers/helpers.php

@@ -2,6 +2,15 @@
 
 use Illuminate\Support\Facades\Lang;
 
+
+if (!function_exists('env')) {
+    function env($key, $default = null)
+    {
+        return env($key, $default); // 直接使用 Laravel 全局 env() 函数
+    }
+}
+
+
 if (!function_exists('list_to_tree')) {
     function list_to_tree($elements, $parentKey = "parent_id", $parentId = 0): array
     {
@@ -57,9 +66,9 @@ if (!function_exists('custom_sort')) {
 }
 
 if (!function_exists('lang')) {
-    function lang(string $key): string
+    function lang(string $key, array $data = []): string
     {
-        $msg = Lang::get("messages.{$key}");
+        $msg = Lang::get("messages.{$key}", $data);
         if ($msg === "messages.{$key}") return $key;
         return $msg;
     }

+ 12 - 0
app/Http/Controllers/admin/ActivityReward.php

@@ -20,6 +20,7 @@ class ActivityReward extends Controller
                 'page' => ['required', 'integer', 'min:1'],
                 'limit' => ['required', 'integer', 'min:1'],
                 'title' => ['nullable', 'string'],
+                'type' => ['nullable', 'string'],
             ]);
             $page = request()->input('page', 1);
             $limit = request()->input('limit', 10);
@@ -28,6 +29,11 @@ class ActivityReward extends Controller
             $count = $query->count();
             $list = $query->orderByDesc('id')
                 ->forPage($page, $limit)->get()->toArray();
+            foreach ($list as &$item) {
+                if (!empty($item['params'])) {
+                    $item['params'] = json_decode($item['params'], true);
+                }
+            }
             $result = ['total' => $count, 'data' => $list];
         } catch (ValidationException $e) {
             return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
@@ -49,8 +55,14 @@ class ActivityReward extends Controller
                 'start_time' => ['required', 'date', 'date_format:Y-m-d'],
                 'end_time' => ['required', 'date', 'date_format:Y-m-d', 'after_or_equal:start_time'],
                 'detail_image' => ['required', 'url', 'regex:/\.(jpeg|jpg|png|webp)$/i'],
+                'pc_image' => ['required', 'url', 'regex:/\.(jpeg|jpg|png|webp)$/i'],
                 'status' => ['required', 'integer', 'min:0', 'max:1'],
+                'type' => ['required'],
+                'lang' => ['required', 'string'],
+                'content' => ['nullable', 'string'],
+                'params' => ['nullable', 'array'],
             ]);
+            $params['params'] = empty($params['params']) ? null : json_encode($params['params']);
             ActivityRewardService::submit($params);
             DB::commit();
         } catch (ValidationException $e) {

+ 2 - 0
app/Http/Controllers/admin/Balance.php

@@ -20,7 +20,9 @@ class Balance extends Controller
             $params = request()->validate([
                 'page' => ['nullable', 'integer', 'min:1'],
                 'limit' => ['nullable', 'integer', 'min:1'],
+                'type' => ['nullable', 'integer'],
                 'member_id' => ['nullable', 'string', 'min:1'],
+                'related_id' => ['nullable', 'string', 'min:1'],
                 'change_type' => ['nullable', 'string', 'in:' . implode(',', BalanceLogService::$RW)],
                 'start_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:end_time'],
                 'end_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:start_time'],

+ 131 - 0
app/Http/Controllers/admin/Banner.php

@@ -0,0 +1,131 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\ValidationException;
+use Exception;
+use App\Models\Banner as BannerModel;
+
+class Banner extends Controller
+{
+
+    /**
+     * @api {get} /admin/banner 轮播图列表
+     * @apiGroup 轮播图管理
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} [page=1]
+     * @apiParam {int} [limit=10]
+     *
+     */
+    function index()
+    {
+        try {
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+            $type = request()->input('type', '');
+            
+            $query = new BannerModel();
+
+            if (!empty($type)) {
+                $query->where('type', $type);
+            }
+            $count = $query->count();
+            $list = $query
+                 ->forPage($page, $limit)
+                ->orderByDesc("sort")
+                ->get()->toArray();
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+    }
+
+    /**
+     * @api {post} /admin/banner/update 修改
+     * @apiDescription 更新或者新增都用这个接口
+     * @apiGroup 轮播图管理
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     */
+    public function update()
+    {
+        DB::beginTransaction();
+        try {
+            $params = request()->validate([
+                'id' => ['nullable'],
+                'title' => ['required'],
+                'image' => ['required'],
+                'link' => ['nullable'],
+                'type' => ['required'],
+                'sort' => ['nullable'],
+                'lang' => ['required'],
+            ]);
+            $id = $params['id'] ?? null;
+
+            BannerModel::updateOrCreate(
+                ['id' => $id],
+                [
+                    'title' => $params['title'],
+                    'image' => $params['image'],
+                    'link' => $params['link'],
+                    'type' => $params['type'],
+                    'sort' => $params['sort'],
+                    'lang' => $params['lang'],
+                ],
+            );
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success();
+    }
+
+
+    /**
+     * @api {post} /admin/banner/delete 删除
+     * @apiGroup 轮播图管理
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} id 要删除的ID
+     *
+     */
+    function delete()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'id' => ['required', 'integer', 'min:1', 'max:99999999'],
+            ]);
+            $id = request()->input('id', 0);
+            $info = BannerModel::where('id', $id)->first();
+            if (!$info) throw new Exception("数据不存在", HttpStatus::CUSTOM_ERROR);
+            $info->delete();
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success();
+    }
+
+
+}

+ 1 - 0
app/Http/Controllers/admin/Config.php

@@ -33,6 +33,7 @@ class Config extends Controller
             $params = request()->validate([
                 'val' => ['required', 'integer', 'min:0', 'in:0,1']
             ]);
+            ConfigModel::updateOrCreate(['field' => 'pc28_switch'], ['val' => $params['val']]);
             Cache::put('pc28_switch', $params['val']);
         } catch (ValidationException $e) {
             return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());

+ 103 - 0
app/Http/Controllers/admin/Level.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\ValidationException;
+use Exception;
+use App\Models\Level as LevelModel;
+
+class Level extends Controller
+{
+
+    /**
+     * @api {get} /admin/level 会员等级列表
+     *
+     */
+    function list()
+    {
+        try {
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+            
+            $query = new LevelModel();
+
+            $count = $query->count();
+            $list = $query
+                 ->forPage($page, $limit)
+                ->orderBy("level")
+                ->get()->toArray();
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+    }
+
+    /**
+     * @api {post} /admin/level/update 修改
+     */
+    public function update()
+    {
+        DB::beginTransaction();
+        try {
+            $params = request()->validate([
+                'id' => ['nullable'],
+                'level' => ['required'],
+                'level_name' => ['required'],
+                'img' => ['required'],
+                'recharge' => ['required'],
+            ]);
+            $id = $params['id'] ?? null;
+
+            LevelModel::updateOrCreate(
+                ['id' => $id],
+                [
+                    'level' => $params['level'],
+                    'level_name' => $params['level_name'],
+                    'img' => $params['img'],
+                    'recharge' => $params['recharge'],
+                ],
+            );
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success();
+    }
+
+
+    /**
+     * @api {post} /admin/level/delete 删除
+     */
+    function delete()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'id' => ['required', 'integer', 'min:1', 'max:99999999'],
+            ]);
+            $id = request()->input('id', 0);
+            $info = LevelModel::where('id', $id)->first();
+            if (!$info) throw new Exception("数据不存在", HttpStatus::CUSTOM_ERROR);
+            $info->delete();
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success();
+    }
+
+
+}

+ 62 - 0
app/Http/Controllers/admin/LhcLottery.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Http\Controllers\Controller;
+use App\Models\LhcLottery as LhcLotteryModel;
+use Exception;
+use App\Constants\HttpStatus;
+
+class LhcLottery extends Controller
+{
+    /**
+     * 开奖管理列表
+     */
+    public function list()
+    {
+        try {
+            $params = request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1'],
+                'type' => ['nullable', 'integer', 'min:1'],
+                'is_settlement' => ['nullable', 'integer'],
+                'issue' => ['nullable', 'string'],
+                'open_code' => ['nullable', 'string'],
+                'start_time' => ['nullable', 'string'],
+                'end_time' => ['nullable', 'string'],
+            ]);
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+
+            $query = new LhcLotteryModel();
+            if (!empty($params['type'])) {
+                $query = $query->where('type', $params['type']);
+            }
+            if (isset($params['is_settlement'])) {
+                $query = $query->where('is_settlement', $params['is_settlement']);
+            }
+            if (!empty($params['issue'])) {
+                $query = $query->where('issue', $params['issue']);
+            }
+            if (!empty($params['open_code'])) {
+                $query = $query->where('open_code', $params['open_code']);
+            }
+            if (!empty($params['start_time'])) {
+                $query = $query->where('open_time', '>=', strtotime($params['start_time'].' 00:00:00'));
+            }
+            if (!empty($params['end_time'])) {
+                $query = $query->where('open_time', '<', strtotime($params['end_time'].' 23:59:59'));
+            }
+            $count = $query->count();
+            $list = $query
+                ->forPage($page, $limit)
+                ->orderByDesc('open_time')
+                ->get();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+
+    }
+
+}

+ 49 - 0
app/Http/Controllers/admin/LhcNumber.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Http\Controllers\Controller;
+use App\Models\LhcNumber as LhcNumberModel;
+use Exception;
+use App\Constants\HttpStatus;
+
+class LhcNumber extends Controller
+{
+    /**
+     * 号码管理列表
+     */
+    public function list()
+    {
+        try {
+            $params = request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1'],
+                'game' => ['nullable', 'string'],
+                'gameplay' => ['nullable', 'string'],
+                'number' => ['nullable'],
+            ]);
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+
+            $query = new LhcNumberModel();
+            if (!empty($params['game'])) {
+                $query = $query->where('game', $params['game']);
+            }
+            if (!empty($params['gameplay'])) {
+                $query = $query->where('gameplay', $params['gameplay']);
+            }
+            if (!empty($params['number'])) {
+                $query = $query->where('number', $params['number']);
+            }
+            $count = $query->count();
+            $list = $query
+                ->forPage($page, $limit)
+                ->get();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+
+    }
+
+}

+ 146 - 0
app/Http/Controllers/admin/LhcOrder.php

@@ -0,0 +1,146 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Http\Controllers\Controller;
+use App\Models\LhcOrder as LhcOrderModel;
+use App\Models\FundsRecord;
+use App\Models\Wallet;
+use Exception;
+use App\Constants\HttpStatus;
+use Illuminate\Support\Facades\DB;
+
+class LhcOrder extends Controller
+{
+
+    /**
+     * 订单列表
+     */
+    public function list()
+    {
+        try {
+            $params = request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1'],
+                'issue' => ['nullable', 'string'],
+                'ordernum' => ['nullable', 'string'],
+                'member_id' => ['nullable', 'integer'],
+                'game' => ['nullable', 'string'],
+                'gameplay' => ['nullable', 'string'],
+                'number' => ['nullable'],
+                'lottery_status' => ['nullable', 'integer'],
+                'is_faker' => ['nullable', 'integer'],
+                'id' => ['nullable', 'integer'],
+            ]);
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+
+            $query = new LhcOrderModel();
+            if (!empty($params['id'])) {
+                $query = $query->where('id', $params['id']);
+            }
+            if (!empty($params['issue'])) {
+                $query = $query->where('issue', $params['issue']);
+            }
+            if (!empty($params['ordernum'])) {
+                $query = $query->where('ordernum', $params['ordernum']);
+            }
+            if (!empty($params['member_id'])) {
+                $query = $query->where('member_id', $params['member_id']);
+            }
+            if (isset($params['lottery_status']) && $params['lottery_status'] !== null) {
+                $query = $query->where('lottery_status', $params['lottery_status']);
+            }
+            if (isset($params['is_faker']) && $params['is_faker'] !== null) {
+                $query = $query->where('is_faker', $params['is_faker']);
+            }
+            if (!empty($params['game'])) {
+                $query = $query->where('game', $params['game']);
+            }
+            if (!empty($params['gameplay'])) {
+                $query = $query->where('gameplay', $params['gameplay']);
+            }
+            if (!empty($params['number'])) {
+                $query = $query->where('number', $params['number']);
+            }
+            $count = $query->count();
+            $list = $query
+                ->forPage($page, $limit)
+                ->orderByDesc('created_at')
+                ->get();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+
+    }
+
+    //订单详情
+    function info()
+    {
+        try {
+            request()->validate([
+                'id' => ['required', 'integer'],
+            ]);
+            $id = request()->input('id');
+            $order = LhcOrderModel::where('id', $id)->first();
+            if (!$order) throw new Exception('订单不存在');
+            $order = $order->toArray();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success($order);
+    }
+
+    /**
+     * @api {post} /lhcorder/refund 同意退款
+     * @apiGroup 订单管理
+     */
+    public function refund()
+    {
+        $errors = [];
+        try {
+            DB::beginTransaction();
+            $params = request()->validate([
+                'id' => ['required', 'array', 'min:1'],
+            ]);
+            $orderList = LhcOrderModel::whereIn('id', $params['id'])->get();
+            foreach ($orderList as $order) {
+                if ($order->lottery_status != 0) {
+                    continue;
+                }
+                
+                $order->lottery_status = 4;
+                $order->updated_at = time();
+                $order->save();
+
+                // 获取用户余额
+                $walletInfo = Wallet::where(['member_id' => $order->member_id])->first();
+                if (!$walletInfo) continue;
+                
+                $before = $walletInfo->available_balance;
+                $after = bcsub($walletInfo->available_balance, $order->amount, 2);
+                $walletInfo->available_balance = $after;
+                $walletInfo->save();
+                
+                $remark = $order->type == 1 ? '澳门六合彩' : '香港六合彩';
+                FundsRecord::addData([
+                    'change_type' => $remark.'退款',
+                    'amount' => $order->amount,
+                    'before_balance' => $before,
+                    'after_balance' => $after,
+                    'member_id' => $order->user_id,
+                    'related_id' => $order->id,
+                    'remark' => $remark.'订单退款',
+                ]);
+            }
+            DB::commit();
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage(), $errors);
+        }
+        return $this->success();
+
+    }
+
+}

+ 154 - 0
app/Http/Controllers/admin/Operation.php

@@ -0,0 +1,154 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Http\Controllers\Controller;
+use Exception;
+use App\Models\User;
+use App\Models\FundsRecord;
+use App\Models\Order;
+use App\Models\Operation as OperationModel;
+use App\Constants\HttpStatus;
+
+class Operation extends Controller
+{
+    /**
+     * 用户报表
+     */
+    function exchangeList()
+    {
+        try {
+            $params = request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1', 'max:200'],
+                'start_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:end_time'],
+                'end_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:start_time'],
+                'user_id' => ['nullable', 'string'],
+            ]);
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+            $query = User::query();
+            if (isset($params['user_id'])) {
+                $user_id = $params['user_id'] ?? null;
+                $query->where(function ($query1) use ($user_id) {
+                    $query1->where('member_id', $user_id)
+                    ->orWhere('first_name', 'like', "%{$user_id}%");
+                });
+            }
+
+            $count = $query->count();
+            $list = $query->join('wallets', 'users.user_id', '=', 'wallets.user_id')
+                ->select(['users.id', 'users.user_id','users.member_id', 'users.first_name', 'wallets.available_balance as money'])
+                ->forPage($page, $limit)
+                ->orderByDesc("users.created_at")
+                ->get()->toArray();
+            
+            $start = !empty($params['start_time']) ? "{$params['start_time']} 00:00:00" : null;
+            $end = !empty($params['end_time']) ? "{$params['end_time']} 23:59:59" : null;
+
+            foreach ($list as &$item) {
+                $item['recharge'] = number_format(FundsRecord::where('member_id', $item['member_id'])
+                    ->whereIn('change_type', ['充值','人工充值','三方充值'])
+                    ->where(function ($query1) use ($start, $end) {
+                        if ($start && $end) {
+                            $query1->where('created_at', '>=', $start)
+                                ->where('created_at', '<=', $end);
+                        }
+                    })
+                    ->sum('amount'), 2);
+
+                $item['withdraw'] = number_format(FundsRecord::where('member_id', $item['member_id'])
+                    ->whereIn('change_type', ['提现','三方提现'])
+                    ->where(function ($query1) use ($start, $end) {
+                        if ($start && $end) {
+                            $query1->where('created_at', '>=', $start)
+                                ->where('created_at', '<=', $end);
+                        }
+                    })
+                    ->sum('amount'), 2);
+                //订单总额
+                $item['order_amount'] = number_format(Order::where('user_id', $item['user_id'])
+                    ->where('status', 1)
+                    ->where('pay_status', 1)
+                    ->where('return_status', 0)
+                    ->where(function ($query1) use ($start, $end) {
+                        if ($start && $end) {
+                            $start = strtotime($start.' 00:00:00');
+                            $end = strtotime($end.' 23:59:59');
+                            $query1->where('create_time', '>=', $start)
+                                ->where('create_time', '<=', $end);
+                        }
+                    })
+                    ->sum('amount'), 2);
+            }
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+
+    }
+
+    /**
+     * 运营数据
+     * @apiParam {String{'day','week','month','all'}} type 类型
+     * - day 今日
+     * - week 本周
+     * - month 本月
+     * - all 全部
+     */
+    function index()
+    {
+        try {
+            $params = request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1', 'max:200'],
+                'type' => ['required', 'string', 'in:day,week,month,all'],
+                'start_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:end_time'],
+                'end_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:start_time'],
+            ]);
+            $data = [
+                'recharge' => 0,
+                'withdraw' => 0,
+                'total_price' => 0,
+                'profit_price' => 0
+            ];
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+            $query = OperationModel::query();
+            $query1 = OperationModel::query();
+            if (!empty($params['start_time'])) {
+                $query->where('date', '>=', $params['start_time'])
+                    ->where('date', '<=', $params['end_time']);
+                $query1->where('date', '>=', $params['start_time'])
+                    ->where('date', '<=', $params['end_time']);
+            } else if ($params['type'] != 'all') {
+                switch ($params['type']) {
+                    case 'day':
+                        $date = date('Y-m-d');
+                        break;
+                    case "week":
+                        $date = date('Y-m-d', strtotime('this week'));
+                        break;
+                    case "month":
+                        $date = date('Y-m-d', strtotime('this month'));
+                        break;
+                }
+                $query->where('date', '>=', $date);
+                $query1->where('date', '>=', $date);
+            }
+            $data['recharge'] = $query1->sum('recharge');
+            $data['withdraw'] = $query1->sum('withdraw');
+            $data['total_price'] = $query1->sum('total_price');
+            $count = $query->count();
+            $list = $query
+                ->forPage($page, $limit)
+                ->orderByDesc('date')
+                ->get()->toArray();
+
+
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list, 'count' => $data]);
+    }
+}

+ 167 - 0
app/Http/Controllers/admin/Order.php

@@ -0,0 +1,167 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Http\Controllers\Controller;
+use App\Models\FundsRecord;
+use App\Models\User;
+use App\Models\Order as OrderModel;
+use Exception;
+use Illuminate\Support\Facades\DB;
+use App\Constants\HttpStatus;
+use App\Models\Wallet;
+
+/**
+ * 足球订单
+ */
+class Order extends Controller
+{
+    /**
+     * 订单手动退款
+     */
+    public function refund()
+    {
+        DB::beginTransaction();
+        try {
+            $params = request()->validate([
+                'id' => ['required', 'array', 'min:1'],
+            ]);
+            $orderList = OrderModel::whereIn('id', $params['id'])->get();
+            foreach ($orderList as $order) {
+                if ($order->return_status != 0 || $order->pay_status != 1 || $order->settlement_status != 0) {
+                    continue;
+                }
+                
+                $order->status = 2;
+                $order->return_status = 2;
+                $order->return_operation_time = time();
+                $order->save();
+                // 获取用户余额
+                $walletInfo = Wallet::where(['member_id' => $order->user_id])->first();
+                if (!$walletInfo) continue;
+                
+                $before = $walletInfo->available_balance;
+                $after = bcsub($walletInfo->available_balance, $order->amount, 2);
+                $walletInfo->available_balance = $after;
+                $walletInfo->save();
+                
+                FundsRecord::addData([
+                    'change_type' => '体彩退款',
+                    'amount' => $order->amount,
+                    'before_balance' => $before,
+                    'after_balance' => $after,
+                    'member_id' => $order->user_id,
+                    'related_id' => $order->id,
+                    'remark' => '体彩订单退款',
+                ]);
+            }
+
+            DB::commit();
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
+        }
+        return $this->success();
+    }
+
+    /**
+     * 订单列表
+     */
+    public function list()
+    {
+        try {
+            $params = request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1'],
+                'id' => ['nullable', 'string'],
+                'user_id' => ['nullable', 'string'],
+                'id' => ['nullable'],
+                'order_id' => ['nullable', 'string'],
+                'issue' => ['nullable', 'integer'],
+                'pay_status' => ['nullable', 'integer', 'in:0,1'],
+                'is_win' => ['nullable', 'integer', 'in:0,1'],
+                'is_roll' => ['nullable', 'integer', 'in:0,1'],
+                'settlement_status' => ['nullable', 'integer', 'in:0,1,2,3'],
+                'return_status' => ['nullable', 'integer', 'in:0,1,2,3'],
+                'status' => ['nullable', 'integer', 'in:0,1,2,-1'],
+                'start_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:end_time'],
+                'end_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:start_time'],
+            ]);
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+
+            $query = OrderModel::join('sport_odds', 'sport_odds.odd_name_en', '=', 'sport_order.odd_name');
+            if (!empty($params['id'])) {
+                $query = $query->where('sport_order.id', $params['id']);
+            }
+            if (!empty($params['start_time'])) {
+                $startTime = strtotime($params['start_time'] . " 00:00:00");
+                $query = $query->where('sport_order.create_time', '>=', $startTime);
+            }
+            if (!empty($params['end_time'])) {
+                $endTime = strtotime($params['end_time'] . " 23:59:59");
+                $query = $query->where('sport_order.create_time', '<=', $endTime);
+            }
+            if (!empty($params['issue'])) {
+                $query = $query->where('sport_order.issue', $params['issue']);
+            }
+            if (!empty($params['order_id'])) {
+                $query = $query->where('order_id', $params['order_id']);
+            }
+            if (!empty($params['user_id'])) {
+                $query = $query->where('sport_order.user_id', $params['user_id']);
+            }
+            if (isset($params['status'])) {
+                $query = $query->where('sport_order.status', $params['status']);
+            }
+            if (isset($params['return_status'])) {
+                $query = $query->where('sport_order.return_status', $params['return_status']);
+            }
+            if (isset($params['pay_status'])) {
+                $query = $query->where('sport_order.pay_status', $params['pay_status']);
+            }
+            if (isset($params['is_win'])) {
+                $query = $query->where('sport_order.is_win', $params['is_win']);
+            }
+            if (isset($params['settlement_status'])) {
+                $query = $query->where('sport_order.settlement_status', $params['settlement_status']);
+            }
+            if (isset($params['is_roll'])) {
+                $query = $query->where('sport_order.is_roll', $params['is_roll']);
+            }
+
+            $count = $query->count();
+            $list = $query
+                ->select(['sport_order.*','sport_odds.odd_name as odd_name'])
+                ->forPage($page, $limit)
+                ->orderByDesc('sport_order.create_time')
+                ->get();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+
+    }
+
+    //订单详情
+    function info()
+    {
+        try {
+            request()->validate([
+                'order_id' => ['required', 'string'],
+            ]);
+            $order_id = request()->input('order_id');
+            $order = OrderModel::where('order_id', $order_id)->first();
+            if (!$order) throw new Exception('订单不存在');
+            $order = $order->toArray();
+            $order['detail'] = json_decode($order['detail'],true);
+            $order['game_result']  = $order['game_result'] ? json_decode($order['game_result'],true) : null;
+            $order['first_name'] = User::where('user_id', $order['user_id'])->value('first_name');
+            ksort($order);
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success($order);
+    }
+
+}

+ 320 - 0
app/Http/Controllers/admin/Sport.php

@@ -0,0 +1,320 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Http\Controllers\Controller;
+use App\Models\Sport as SportModel;
+use App\Models\SportEvent;
+use App\Models\SportTeam;
+use App\Models\SportLeague;
+use App\Models\SportStatistics;
+use Exception;
+use App\Constants\HttpStatus;
+use App\Services\SportClientService;
+
+
+class Sport extends Controller
+{
+    
+    /**
+     * 列表
+     */
+    public function list()
+    {
+        try {
+            $params = request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1'],
+                'data_id' => ['nullable', 'string'],
+                'team_name' => ['nullable', 'integer'],
+                'league' => ['nullable', 'string'],
+                'state' => ['nullable', 'integer'],
+                'status' => ['nullable', 'integer'],
+                'is_locked' => ['nullable', 'integer'],
+                'start_time' => ['nullable', 'string'],
+                'end_time' => ['nullable', 'string'],
+                'error' => ['nullable', 'integer'],
+                'odds' => ['nullable', 'integer'],
+                'is_rec' => ['nullable', 'integer'],
+            ]);
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+            $order = 'asc';
+
+            $query = new SportModel();
+            if (!empty($params['data_id'])) {
+                $query = $query->where('data_id', $params['data_id']);
+            }
+            if (!empty($params['team_name'])) {
+                $team_name = $params['team_name'];
+                $query = $query->where(function ($query) use ($team_name) {
+                    $query->where('team_name', 'like', "%{$team_name}%")
+                    ->orWhere('team_name_en', 'like', "%{$team_name}%")
+                    ->orWhere('guest_team', 'like', "%{$team_name}%")
+                    ->orWhere('guest_team_en', 'like', "%{$team_name}%");
+                });
+            }
+            if (isset($params['league']) && $params['league'] !== null) {
+                $league = $params['league'];
+                $query = $query->where(function ($query) use ($league) {
+                    $query->where('league', 'like', "%{$league}%")
+                    ->orWhere('league_en', 'like', "%{$league}%");
+                });
+            }
+            if (isset($params['state']) && $params['state'] !== null) {
+                $query = $query->where('state', $params['state']);
+                if ($params['state'] >= 2) {
+                    $order = 'desc';
+                }
+            }
+            if (isset($params['status']) && $params['status'] !== null) {
+                $query = $query->where('status', $params['status']);
+            }
+            if (isset($params['is_locked']) && $params['is_locked'] !== null) {
+                $query = $query->where('is_locked', $params['is_locked']);
+            }
+            if (!empty($params['start_time'])) {
+                $query = $query->where('game_time', '>=', strtotime($params['start_time'].' 00:00:00'));
+            }
+            if (!empty($params['end_time'])) {
+                $query = $query->where('game_time', '<', strtotime($params['end_time'].' 23:59:59'));
+            }
+            if (isset($params['error']) && $params['error'] !== null) {
+                $query = $query->where('error', $params['error']);
+                $order = 'desc';
+            }
+            if (isset($params['odds'])) {
+                if ($params['odds'] == 1) {
+                    $query = $query->whereNotNull('odds');
+                } elseif ($params['odds'] == 0) {
+                    $query = $query->whereNull('odds');
+                }
+            }
+            if (isset($params['is_rec']) && $params['is_rec'] !== null) {
+                $query = $query->where('is_rec', $params['is_rec']);
+            }
+
+            $count = $query->count();
+            $list = $query
+                ->forPage($page, $limit)
+                ->orderBy('game_time', $order)
+                ->orderBy('is_roll', 'desc')
+                ->get();
+            foreach($list as &$item) {
+                $item['game_time'] = date('Y-m-d H:i:s', $item['game_time']);
+                $item['rbt'] = date('Y-m-d H:i:s', $item['rbt']);
+                $item['fixture_status'] = json_decode($item['fixture_status'], true);
+                
+                $item['home_team'] = SportTeam::getTeamName($item['home_team_id']) ?? $item['home_team_en'];
+                $item['guest_team'] = SportTeam::getTeamName($item['guest_team_id']) ?? $item['guest_team_en'];
+                $item['league'] = SportLeague::getLeagueName($item['league_id']) ?? $item['league_en'];
+            }
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+
+    }
+
+    //详情
+    public function info()
+    {
+        try {
+            request()->validate([
+                'id' => ['required', 'integer'],
+            ]);
+            $id = request()->input('id');
+            $info = SportModel::where('id', $id)->first();
+            if (!$info) throw new Exception('赛事不存在');
+            $info = $info->toArray();
+            $info['date'] = date('Y-m-d',$info['game_time']);
+            $info['time'] = date('H:i',$info['game_time']);
+            $info['game_time'] = date('Y-m-d H:i:s', $info['game_time']);
+            $info['rbt'] = date('Y-m-d H:i:s', $info['rbt']);
+            $info['fixture_status'] = json_decode($info['fixture_status'], true);
+            $info['odds'] = $info['odds'] ? json_decode($info['odds'], true) : null;
+            $info['odds'] = $info['odds'] ? SportModel::doOdds($info['odds'], $info['odd_values_locked']) : null;
+            $info['event'] = SportEvent::where('data_id', $info['data_id'])->get();
+
+            $info['home_team'] = $info['home_team'] ?? SportTeam::getTeamName($info['home_team_id']);
+            $info['guest_team'] = $info['guest_team'] ?? SportTeam::getTeamName($info['guest_team_id']);
+            $info['league'] = $info['league'] ?? SportLeague::getLeagueName($info['league_id']);
+
+            //赛事统计数据
+            $info['statistics'] = SportStatistics::where('data_id', $info['data_id'])->get()->toArray();
+
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success($info);
+    }
+
+    public function setStatus()
+    {
+        try {
+            $params = request()->validate([
+                'id' => ['required','array'],
+                'status' => ['nullable', 'integer'],
+                'state' => ['nullable', 'integer'],
+                'is_locked' => ['nullable', 'integer'],
+                'is_rec' => ['nullable', 'integer'],
+            ]);
+            $id = $params['id'];
+            if (isset($params['state']) && $params['state'] !== null) {
+                $update_data = ['state' => $params['state'], 'is_send' => 0];
+                if ($params['state'] > 2) {
+                    $update_data['refund_status'] = 1;
+                }
+                SportModel::whereIn('id', $id)->update($update_data);
+            }
+            if (isset($params['is_locked']) && $params['is_locked'] !== null) {
+                SportModel::whereIn('id', $id)->update(['is_locked' => $params['is_locked'], 'is_send' => 0]);
+            }
+            if (isset($params['status']) && $params['status'] !== null) {
+                SportModel::whereIn('id', $id)->update(['status' => $params['status'], 'is_send' => 0]);
+            }
+            if (isset($params['is_rec']) && $params['is_rec'] !== null) {
+                SportModel::whereIn('id', $id)->update(['is_rec' => $params['is_rec']]);
+            }
+            return $this->success();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+    }
+
+    //设置赔率玩法是否加锁
+    public function setOddsLocked()
+    {
+        try {
+            $params = request()->validate([
+                'id' => ['required','integer'],
+                'odd_id' => ['nullable','integer'],
+            ]);
+            $id = $params['id'];
+            $info = SportModel::where('id', $id)->first();
+            if (!$info) throw new Exception('赛事不存在');
+
+            $odd_ids_locked = $info->odd_ids_locked;
+            if (empty($params['odd_id'])) {
+                if (empty($odd_ids_locked) && !empty($info->odds)) {
+                    $odds = json_decode($info->odds, true);
+                    foreach ($odds as $odd) {
+                        $odd_ids_locked[] = $odd['id'];
+                    }
+                } else {
+                    $odd_ids_locked = [];
+                }
+            } else {
+                if (in_array($params['odd_id'], $odd_ids_locked)) {
+                    $odd_ids_locked = array_diff($odd_ids_locked, [$params['odd_id']]);
+                    $odd_ids_locked = array_values($odd_ids_locked);
+                } else {
+                    $odd_ids_locked[] = $params['odd_id'];
+                }
+            }
+            $info->odd_ids_locked = json_encode($odd_ids_locked);
+            $info->is_send = 0;
+            $info->save();
+            return $this->success();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+    }
+
+    //设置下注项是否加锁
+    public function setValuesLocked()
+    {
+        try {
+            $params = request()->validate([
+                'id' => ['required','integer'],
+                'odd_id' => ['required','integer'],
+                'value' => ['required'],
+                'handicap' => ['nullable'],
+            ]);
+            $id = $params['id'];
+            $odd_id = $params['odd_id'];
+            $value = $params['value'];
+            $handicap = $params['handicap'] ?? "";
+            $info = SportModel::where('id', $id)->first();
+            if (!$info) throw new Exception('赛事不存在');
+
+            $odd_values_locked = $info->odd_values_locked;
+
+            $vh = $value."_".$handicap;
+            if (isset($odd_values_locked[$odd_id]) && ($key = array_search($vh, $odd_values_locked[$odd_id])) !== false) {
+                //查找数组是否存在这个值,存在则删除这个数据
+                unset($odd_values_locked[$odd_id][$key]);
+            } else {
+                $odd_values_locked[$odd_id][] = $vh;
+            }
+            $info->odd_values_locked = json_encode($odd_values_locked);
+            $info->is_send = 0;
+            $info->save();
+            return $this->success();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+    }
+
+    public function getFixtures()
+    {
+        $params = request()->validate([
+            'data_id' => ['required'],
+        ]);
+        $data_id = $params['data_id'];
+        $info = SportModel::where('data_id', $data_id)->first();
+        if (!$info) throw new Exception('赛事不存在');
+
+        $data = SportClientService::fixtures([
+            'id' => $data_id,
+        ]);
+        $data = !empty($data['response'][0]) ? $data['response'][0] : [];
+        $result = [];
+        if (!empty($data['fixture'])) {
+            $result['fixture'] = $data['fixture'];
+            if (!empty($result['fixture']['periods']['first'])) {
+                $result['fixture']['periods']['first'] = date('Y-m-d H:i:s', $result['fixture']['periods']['first']);
+            }
+            if (!empty($result['fixture']['periods']['second'])) {
+                $result['fixture']['periods']['second'] = date('Y-m-d H:i:s', $result['fixture']['periods']['second']);
+            }
+            if (!empty($result['fixture']['status']['long'])) {
+                $result['fixture']['status']['long'] = SportModel::getLongStatus($result['fixture']['status']['long']);
+            }
+        }
+        if (!empty($data['goals'])) {
+            $result['goals'] = $data['goals'];
+        }
+        if (!empty($data['score'])) {
+            $result['score'] = $data['score'];
+        }
+        if (!empty($data['events'])) {
+            $result['events'] = $data['events'];
+            $result['events'] = $this->doEvent($result['events'], $data_id);
+        }
+        
+        $result['info'] = $info->toArray();
+        $result['info']['game_time'] = date('Y-m-d H:i:s', $info->game_time);
+        return $this->success($result);
+    }
+
+    private function doEvent($events, $data_id) {
+        if (empty($events)) return [];
+        $result = [];
+        foreach($events as $event) {
+            $result[] = [
+                'data_id' => $data_id,
+                'type' => $event['type'],
+                'time_elapsed' => $event['time']['elapsed'],
+                'time' => $event['time'],
+                'detail' => $event['detail'],
+                'player' => $event['player'],
+                'team_id' => $event['team']['id'],
+                'comments' => $event['comments'],
+                'assist' => $event['assist'],
+            ];
+        }
+        return $result;
+    }
+
+}

+ 50 - 4
app/Http/Controllers/admin/User.php

@@ -4,21 +4,19 @@ namespace App\Http\Controllers\admin;
 
 use App\Constants\HttpStatus;
 use App\Http\Controllers\Controller;
-use App\Services\RoomService;
 use App\Services\SecretService;
 use App\Services\TopUpService;
 use Illuminate\Support\Facades\App;
-use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Validator;
 use App\Services\UserService;
 use Exception;
-use Illuminate\Http\Request;
 use Illuminate\Validation\ValidationException;
 use App\Services\AddressService;
 
 use Illuminate\Http\JsonResponse;
 use App\Models\User as UserModel;
+use App\Models\UserSession;
+use App\Models\UserLogin;
 
 class User extends Controller
 {
@@ -30,6 +28,11 @@ class User extends Controller
                 'is_banned' => ['required', 'integer', 'in:0,1'],
             ]);
             UserModel::where('member_id', $params['member_id'])->update(['is_banned' => $params['is_banned']]);
+            if ($params['is_banned'] == 1) {
+                //如果用户被禁用,删除所有会话
+                UserSession::where('user_id', $params['member_id'])->delete();
+                return $this->success();
+            }
         } catch (ValidationException $e) {
             return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
         } catch (Exception $e) {
@@ -69,6 +72,9 @@ class User extends Controller
                 'register_ip' => ['nullable', 'string', 'min:1'],
                 'order' => ["nullable", 'string', "in:asc,desc"],
                 'by' => ['nullable', 'string', "in:available_balance,created_at,last_active_time"],
+                'user_code' => ['nullable'],
+                'agent_user_code' => ['nullable'],
+                'level' => ['nullable'],
             ]);
             $order = request()->input('order', 'desc');
             $by = request()->input('by', 'available_balance');
@@ -148,4 +154,44 @@ class User extends Controller
         return $this->success($result);
     }
 
+    /**
+     * 用户登录日志
+     */
+    public function loginLog()
+    {
+        try {
+            $params = request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1'],
+                'user_id' => ['nullable'],
+                'start_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:end_time'],
+                'end_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:start_time'],
+            ]);
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+
+            $query = new UserLogin();
+            if (!empty($params['user_id'])) {
+                $query = $query->where('user_id', $params['user_id']);
+            }
+            if (!empty($params['start_time'])) {
+                $startTime = $params['start_time'] . " 00:00:00";
+                $query = $query->where('created_at', '>=', $startTime);
+            }
+            if (!empty($params['end_time'])) {
+                $endTime = $params['end_time'] . " 23:59:59";
+                $query = $query->where('updated_at', '<=', $endTime);
+            }
+            $count = $query->count();
+            $list = $query
+                ->forPage($page, $limit)
+                ->orderByDesc('created_at')
+                ->get();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+
+    }
+
 }

+ 123 - 11
app/Http/Controllers/admin/Wallet.php

@@ -16,19 +16,10 @@ use Illuminate\Support\Facades\DB;
 use Illuminate\Validation\ValidationException;
 use App\Models\Wallet as WalletModel;
 use App\Models\Withdraw;
-use App\Services\BetService;
-use App\Services\IssueService;
-use App\Services\GameplayRuleService;
 use App\Models\Config;
-use App\Services\ConfigService;
-use App\Services\KeyboardService;
-use App\Services\Payment\QianBaoService;
-use App\Services\Payment\SanJinService;
-use App\Services\PaymentOrderService;
-use Firebase\JWT\Key;
-use Google\Service\Adsense\Payment;
 use App\Models\PaymentOrder;
-use App\Services\WalletService;
+use App\Models\Order;
+use App\Models\LhcOrder;
 
 class Wallet extends Controller
 {
@@ -70,6 +61,7 @@ class Wallet extends Controller
         return $this->success($data);
     }
 
+    //扣款
     public function debiting()
     {
         DB::beginTransaction();
@@ -97,6 +89,7 @@ class Wallet extends Controller
             BalanceLogService::addLog($memberId, $amount, $wallet->available_balance, $availableBalance, "人工扣款", null, $remark);
             $wallet->available_balance = $availableBalance;
             $wallet->save();
+
             DB::commit();
         } catch (ValidationException $e) {
             DB::rollBack();
@@ -112,6 +105,7 @@ class Wallet extends Controller
         return $this->success();
     }
 
+    //会员账号充值
     public function topUp()
     {
         DB::beginTransaction();
@@ -139,6 +133,7 @@ class Wallet extends Controller
             BalanceLogService::addLog($memberId, $amount, $wallet->available_balance, $availableBalance, $changeType, null, $remark);
             $wallet->available_balance = $availableBalance;
             $wallet->save();
+
             DB::commit();
         } catch (ValidationException $e) {
             DB::rollBack();
@@ -155,6 +150,118 @@ class Wallet extends Controller
         return $this->success();
     }
 
+    //订单充值
+    public function orderTopUp()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'amount' => ['required', 'numeric', 'min:0.01'],
+                'remark' => ['required', 'string', 'min:1'],
+                'order_type' => ['nullable'],
+                'order_id' => ['nullable'],
+            ]);
+            $order_type = request()->input('order_type');
+            $order_id = request()->input('order_id');
+            $amount = request()->input('amount');
+            $remark = request()->input('remark');
+            $changeType = "人工充值";
+            $key = 'api_request_' . md5($order_id . json_encode([
+                        'amount' => $amount,
+                        'remark' => $remark,
+                        'action' => "wallet/orderTopUp"
+                    ]));
+            if (Cache::has($key)) throw new Exception("请求太频繁,请稍后再试。", HttpStatus::CUSTOM_ERROR);
+            Cache::put($key, true, 3);
+
+            if ($order_type == 'sport') {
+                $info = Order::where('id', $order_id)->first();
+            } elseif ($order_type == 'lhc') {
+                $info = LhcOrder::where('id', $order_id)->first();
+            }
+            if (!$info) {
+                throw new \Exception('订单不存在');
+            } else {
+                $info->remark = $info->remark ? $info->remark. '|' . $remark : $remark;
+                $info->save();
+            }
+            $memberId = $order_type == 'sport' ? $info->user_id : $info->member_id;
+
+            $wallet = WalletModel::where('member_id', $memberId)->first();
+            if (!$wallet) throw new Exception('用户不存在', HttpStatus::CUSTOM_ERROR);
+            $availableBalance = bcadd($wallet->available_balance, $amount, 10);
+            BalanceLogService::addLog($memberId, $amount, $wallet->available_balance, $availableBalance, $changeType, null, $remark);
+            $wallet->available_balance = $availableBalance;
+            $wallet->save();
+
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+
+        return $this->success();
+    }
+
+    //订单扣款
+    public function orderDebiting()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'amount' => ['required', 'numeric', 'min:0.01'],
+                'remark' => ['required', 'string', 'min:1'],
+                'order_type' => ['required'],
+                'order_id' => ['required'],
+            ]);
+            $order_type = request()->input('order_type');
+            $order_id = request()->input('order_id');
+            $amount = request()->input('amount');
+            $amount = $amount * -1;
+            $remark = request()->input('remark');
+            $key = 'api_request_' . md5($order_id . json_encode([
+                        'amount' => $amount,
+                        'remark' => $remark,
+                        'action' => "wallet/orderDebiting"
+                    ]));
+            if (Cache::has($key)) throw new Exception("请求太频繁,请稍后再试。", HttpStatus::CUSTOM_ERROR);
+            Cache::put($key, true, 3);
+
+            if ($order_type == 'sport') {
+                $info = Order::where('id', $order_id)->first();
+            } elseif ($order_type == 'lhc') {
+                $info = LhcOrder::where('id', $order_id)->first();
+            }
+            if (!$info) {
+                throw new \Exception('订单不存在');
+            } else {
+                $info->remark = $info->remark ? $info->remark. '|' . $remark : $remark;
+                $info->save();
+            }
+            $memberId = $order_type == 'sport' ? $info->user_id : $info->member_id;
+
+            $wallet = WalletModel::where('member_id', $memberId)->first();
+            if (!$wallet) throw new Exception('用户不存在', HttpStatus::CUSTOM_ERROR);
+            $availableBalance = bcadd($wallet->available_balance, $amount, 10);
+            if ($availableBalance < 0) throw new Exception('可用余额不足', HttpStatus::CUSTOM_ERROR);
+            BalanceLogService::addLog($memberId, $amount, $wallet->available_balance, $availableBalance, "人工扣款", null, $remark);
+            $wallet->available_balance = $availableBalance;
+            $wallet->save();
+
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success();
+    }
+
     /**
      * @api {post} /admin/wallet/verifyRecharge 审核
      * @apiGroup 充值管理
@@ -284,6 +391,11 @@ class Wallet extends Controller
             ]);
             $search = request()->all();
             $result = RechargeService::paginate($search);
+            foreach($result['data'] as &$item) {
+                if ($item['from'] == 2) {
+                    $item['image'] = str_replace(env('APP_URL'), env('FOOTBALL_APP_URL').'/', $item['image']);
+                }
+            }
         } catch (ValidationException $e) {
             return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
         } catch (Exception $e) {

+ 110 - 0
app/Http/Controllers/admin/YueBao.php

@@ -0,0 +1,110 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Http\Controllers\Controller;
+use App\Models\YuebaoItem;
+use App\Models\YuebaoLog;
+use Exception;
+use App\Constants\HttpStatus;
+
+class YueBao extends Controller
+{
+
+    /**
+     * 余额宝持仓列表
+     */
+    public function item()
+    {
+        try {
+            $params = request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1'],
+                'id' => ['nullable', 'integer', 'min:1'],
+                'user_id' => ['nullable'],
+                'start_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:end_time'],
+                'end_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:start_time'],
+            ]);
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+
+            $query = new YuebaoItem();
+            if (!empty($params['id'])) {
+                $query = $query->where('id', $params['id']);
+            }
+            if (!empty($params['user_id'])) {
+                $query = $query->where('user_id', $params['user_id']);
+            }
+            if (!empty($params['start_time'])) {
+                $startTime = strtotime($params['start_time'] . " 00:00:00");
+                $query = $query->where('create_time', '>=', $startTime);
+            }
+            if (!empty($params['end_time'])) {
+                $endTime = strtotime($params['end_time'] . " 23:59:59");
+                $query = $query->where('create_time', '<=', $endTime);
+            }
+            $count = $query->count();
+            $list = $query
+                ->forPage($page, $limit)
+                ->orderByDesc('create_time')
+                ->get();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+
+    }
+
+    /**
+     * 余额宝日志列表
+     */
+    public function log()
+    {
+        try {
+            $params = request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1'],
+                'id' => ['nullable', 'integer', 'min:1'],
+                'item_id' => ['nullable', 'integer', 'min:1'],
+                'user_id' => ['nullable'],
+                'type' => ['nullable', 'integer'],
+                'start_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:end_time'],
+                'end_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:start_time'],
+            ]);
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 15);
+
+            $query = new YuebaoLog();
+            if (!empty($params['id'])) {
+                $query = $query->where('id', $params['id']);
+            }
+            if (!empty($params['item_id'])) {
+                $query = $query->where('item_id', $params['item_id']);
+            }
+            if (!empty($params['user_id'])) {
+                $query = $query->where('user_id', $params['user_id']);
+            }
+            if (!empty($params['type'])) {
+                $query = $query->where('type', $params['type']);
+            }
+            if (!empty($params['start_time'])) {
+                $startTime = strtotime($params['start_time'] . " 00:00:00");
+                $query = $query->where('create_time', '>=', $startTime);
+            }
+            if (!empty($params['end_time'])) {
+                $endTime = strtotime($params['end_time'] . " 23:59:59");
+                $query = $query->where('create_time', '<=', $endTime);
+            }
+            $count = $query->count();
+            $list = $query
+                ->forPage($page, $limit)
+                ->orderByDesc('create_time')
+                ->get();
+        } catch (Exception $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
+        }
+        return $this->success(['total' => $count, 'data' => $list]);
+
+    }
+
+}

+ 2 - 1
app/Http/Controllers/api/ActivityReward.php

@@ -168,7 +168,8 @@ class ActivityReward extends BaseController
             $query = ActivityRewardModel::where(ActivityRewardService::getWhere([
 //                'start_time' => ['<=', $time],
                 'end_time' => ['>=', $time],
-                'status' => ActivityRewardModel::STATUS_UP
+                'status' => ActivityRewardModel::STATUS_UP,
+                'type' => 1,
             ]));
             if (!empty($memberId)) {
                 $query->with(['activityUser' => function ($query1) use ($memberId) {

+ 4 - 1
app/Http/Controllers/api/BaseController.php

@@ -17,7 +17,10 @@ class BaseController extends Controller
 
     public function __construct()
     {
-        $lang = request()->input('lang', 'zh');
+        $lang = request()->input('lang');
+        if (!$lang) {
+            $lang = request()->header('Lang', 'zh');
+        }
         App::setLocale($lang);
         $this->lang = $lang;
     }

+ 36 - 0
app/Http/Controllers/api/Game.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Http\Controllers\api;
+
+use App\Models\GameplayRule;
+use Illuminate\Validation\ValidationException;
+use Exception;
+
+class Game extends BaseController
+{
+    /**
+     * @api {get} /game/rule 玩法规则
+     */
+    function rule()
+    {
+        try {
+            request()->validate([
+                'page' => ['nullable', 'integer', 'min:1'],
+                'limit' => ['nullable', 'integer', 'min:1']
+            ]);
+            $page = request()->input('page', 1);
+            $limit = request()->input('limit', 10);
+            $list = GameplayRule::forPage($page, $limit)
+                ->orderBy('groups')
+                ->orderByDesc('created_at')
+                ->get();
+            
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error($e->getMessage());
+        }
+        return $this->success($list);
+    }
+
+}

+ 19 - 0
app/Http/Controllers/api/Issue.php

@@ -179,7 +179,9 @@ class Issue extends BaseController
         $arr['c'] = $winnings[2];
 
         $arr['size'] = in_array('大', $award) ? '大' : "小";
+        $arr['size'] = lang($arr['size']);
         $arr['odd_or_even'] = in_array('单', $award) ? '单' : "双";
+        $arr['odd_or_even'] = lang($arr['odd_or_even']);
         $arr['issue_no'] = $data2->issue_no;
         $data = [
             'issue_no' => $issue_no2,
@@ -230,6 +232,23 @@ class Issue extends BaseController
             $res = IssueService::paginate($params);
             foreach ($res['data'] as &$item) {
                 $item['day'] = date("m-d H:i", strtotime($item['end_time']));
+                if ($item['combo']) {
+                    $combo = explode(" ", $item['combo']);
+                    foreach($combo as &$v) {
+                        if (stripos($v, '尾') !== false) {
+                            $v = str_replace('尾', lang('尾'), $v);
+                        } elseif (stripos($v, '头') !== false) {
+                            $v = str_replace('头', lang('头'), $v);
+                        } elseif (stripos($v, '操') !== false) {
+                            $v = str_replace('操', lang('操'), $v);
+                        } else {
+                            $v = lang($v);
+                        }
+                    }
+                    $item['combo'] = $combo;
+                } else {
+                    $item['combo'] = [];
+                }
             }
         } catch (ValidationException $e) {
             return $this->error($e->validator->errors()->first());

+ 31 - 2
app/Http/Controllers/api/NewPc.php

@@ -185,7 +185,9 @@ class NewPc extends BaseController
         $arr['c'] = $winnings[2];
 
         $arr['size'] = in_array('大', $award) ? '大' : "小";
+        $arr['size'] = lang($arr['size']);
         $arr['odd_or_even'] = in_array('单', $award) ? '单' : "双";
+        $arr['odd_or_even'] = lang($arr['odd_or_even']);
         $arr['issue_no'] = $data2->issue_no;
         $data = [
             'issue_no' => $issue_no2,
@@ -227,11 +229,38 @@ class NewPc extends BaseController
         try {
             $page = request()->input('page', 1);
             $limit = request()->input('limit', 10);
-            $res['total'] = PcIssue::count();
-            $res['data'] = PcIssue::forPage($page, $limit)
+            $where = [];
+            $start_time = request()->input('start_time', '');
+            $end_time = request()->input('end_time', '');
+            $where = [];
+            if (!empty($start_time)) {
+                $where[] = ['end_time', '>=', $start_time.' 00:00:00'];
+            }
+            if (!empty($end_time)) {
+                $where[] = ['end_time', '<=', $end_time.' 23:59:59'];
+            }
+            $res['total'] = PcIssue::where($where)->count();
+            $res['data'] = PcIssue::where($where)->forPage($page, $limit)
                 ->orderByDesc('id')->get();
             foreach ($res['data'] as &$item) {
                 $item['day'] = date("m-d H:i", strtotime($item['end_time']));
+                if ($item['combo']) {
+                    $combo = explode(" ", $item['combo']);
+                    foreach($combo as &$v) {
+                        if (stripos($v, '尾') !== false) {
+                            $v = str_replace('尾', lang('尾'), $v);
+                        } elseif (stripos($v, '头') !== false) {
+                            $v = str_replace('头', lang('头'), $v);
+                        } elseif (stripos($v, '操') !== false) {
+                            $v = str_replace('操', lang('操'), $v);
+                        } else {
+                            $v = lang($v);
+                        }
+                    }
+                    $item['combo'] = $combo;
+                } else {
+                    $item['combo'] = [];
+                }
             }
         } catch (ValidationException $e) {
             return $this->error($e->validator->errors()->first());

+ 118 - 0
app/Http/Controllers/api/PcIssue.php

@@ -0,0 +1,118 @@
+<?php
+
+namespace App\Http\Controllers\api;
+
+use App\Models\Config;
+use Carbon\Carbon;
+use Illuminate\Http\JsonResponse;
+use App\Http\Controllers\api\Issue;
+use App\Http\Controllers\api\NewPc;
+
+/**
+ * pc28,根据配置的游戏玩法返回指定的数据
+ */
+class PcIssue extends BaseController
+{
+
+    /**
+     * @api {get} /pcissue/yuanTou 源头
+     *
+     */
+    function yuanTou()
+    {
+        $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
+        if ($pc28Switch == 1) {
+            //pc28分分彩/极速28
+            $issueController = new NewPc();
+        } else {
+            //pc28
+            $issueController = new Issue();
+        }
+        return $issueController->yuanTou();
+    }
+
+
+    /**
+     * @api {get} /pcissue/history 天机
+     *
+     */
+    function history()
+    {
+        $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
+        if ($pc28Switch == 1) {
+            //pc28分分彩/极速28
+            $issueController = new NewPc();
+        } else {
+            //pc28
+            $issueController = new Issue();
+        }  
+        return $issueController->history();
+    }
+
+    /**
+     * @api {get} /pcissue/prediction 预测
+     */
+    function prediction()
+    {
+        $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
+        if ($pc28Switch == 1) {
+            //pc28分分彩/极速28
+            $issueController = new NewPc();
+        } else {
+            //pc28
+            $issueController = new Issue();
+        }
+        return $issueController->prediction();
+    }
+
+    /**
+     * @api {get} /pcissue/countdown 倒计时
+     *
+     */
+    public function countdown(): JsonResponse
+    {
+        //0:pc28 1:急速28
+        $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
+        if ($pc28Switch == 1) {
+            $issueController = new NewPc();
+        } else {
+            //pc28
+            $issueController = new Issue();
+        }
+        return $issueController->countdown();
+    }
+
+
+    /**
+     * @api {get} /pcissue 结果,走势
+     */
+    public function index()
+    {
+        //0:pc28 1:急速28
+        $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
+        if ($pc28Switch == 1) {
+            $issueController = new NewPc();
+        } else {
+            //pc28
+            $issueController = new Issue();
+        }
+        return $issueController->index();
+    }
+
+    /**
+     * @api {get} /pcissue/cao 统计,历史
+     */
+    public function cao()
+    {
+        $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
+        if ($pc28Switch == 1) {
+            //pc28分分彩/极速28
+            $issueController = new NewPc();
+        } else {
+            //pc28
+            $issueController = new Issue();
+        }
+        return $issueController->cao();
+    }
+
+}

+ 436 - 0
app/Http/Controllers/api/Wallet.php

@@ -0,0 +1,436 @@
+<?php
+
+namespace App\Http\Controllers\api;
+
+use App\Services\WalletService;
+use App\Services\ConfigService;
+use App\Models\Config;
+use App\Models\Recharge;
+use App\Models\Wallet as WalletModel;
+use App\Models\Withdraw;
+use App\Models\User;
+use App\Models\Bank;
+use App\Models\Address;
+use App\Services\BalanceLogService;
+use App\Services\Payment\SanJinService;
+use App\Services\PaymentOrderService;
+use App\Services\QianBaoWithdrawService;
+use App\Services\Payment\QianBaoService;
+use App\Services\WithdrawService;
+
+
+
+use Illuminate\Validation\ValidationException;
+
+use Exception;
+
+/**
+ * 充值提现接口
+ */
+class Wallet extends BaseController
+{
+
+    //获取三斤充值通道(微信、支付宝、扫码充值)
+    public function getChannel()
+    {
+        $data = SanJinService::getChannel();
+        $product = SanJinService::$PRODUCT;
+        $list = [];
+        foreach($data as $k => $v) {
+            foreach($product as $pv) {
+                if ($k == $pv['type']) {
+                    $config = $pv;
+                }
+            }
+            $list[] = [
+                'label' => lang($v),
+                'value' => $k,
+                'config' => $config ?? [],
+            ];
+        }
+        return $this->success([
+            'list' => $list,
+        ]);
+    }
+
+    /**
+     * 创建代收订单
+     */
+    public function createPay()
+    {   
+        try {
+            $params = request()->validate([
+                'amount' => ['required', 'numeric', 'min:0.01'],
+                'payment_type' => ['required', 'string'],
+            ]);
+            $member_id = request()->user->member_id;
+            $res = PaymentOrderService::createPay($member_id, $params['amount'], $params['payment_type']);
+            if ($res['code'] == 0) { 
+                return $this->success($res);
+            }
+            return $this->error($res['text']);
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+    /**
+     * 获取充值二维码(USDT充值)
+     */
+    public function scan()
+    {
+        try {
+            $member_id = request()->user->member_id;
+            $params = request()->validate([
+                'type' => ['required', 'string'],
+            ]);
+            $receivingType = ConfigService::getVal("receiving_type");
+            //自动充值
+            if ($receivingType == 1) {
+                $res = WalletService::getRechargeImageAddress($member_id);
+                $address = $res['address'];
+                $qrCode = $res['full_path'];
+            } else {
+                //手动充值
+                if (strtolower($params['type']) === "trc20") {
+                    $address = Config::where('field', 'receiving_address')->first()->val;
+                } elseif (strtolower($params['type']) === "erc20") {
+                    $address = Config::where('field', 'receiving_address_erc20')->first()->val;
+                } else {
+                    return $this->error(lang('充值类型错误'));
+                }
+                $res = WalletService::getPlatformImageAddress($address);
+                $res['net'] = $params['type'];
+                $qrCode = $res['full_path'];
+            }
+            
+            return $this->success([
+                'qrcode' => $qrCode,
+                // 'photo' => InputFile::create($qrCode),
+            ]);
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+    /**
+     * 提交充值凭证
+     */
+    public function recharge()
+    {
+        try {
+            $params = request()->validate([
+                'net' => ['required', 'string'],
+                'amount' => ['required', 'numeric', 'min:0.01'],
+                'toAddress' => ['required', 'string'],
+                'image' => ['required', 'url'],
+            ]);
+            $member_id = request()->user->member_id;
+            $recharge = new Recharge();
+            $recharge->member_id = $member_id;
+            $recharge->net = $params['net'];
+            $recharge->coin = "USDT";
+            $recharge->amount = $params['amount'];
+            $recharge->to_address = $params['toAddress'];
+            $recharge->status = 0;
+            $recharge->type = 2;
+            $recharge->image = $params['image'];
+            $recharge->save();
+            return $this->success($recharge,'提交成功');
+
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+    /**
+     * 获取提现通道
+     */
+    public function withdrawChannel()
+    {
+        $list = QianBaoService::withdrawChannel();
+        $data[] = ['label' => 'USDT', 'value' => 'USDT'];
+        foreach ($list as $key => $item) {
+            $data[] = ['label' => $item, 'value' => $key];
+        }
+        return $this->success($data);
+    }
+
+    public function withdraw()
+    {   
+        try {
+            $member_id = request()->user->member_id;
+            $params = request()->validate([
+                'amount' => ['required', 'numeric', 'min:0.01'],
+                'address' => ['required', 'string'],
+                'safe_word' => ['required'],
+            ]);
+            $user = User::where('member_id', $member_id)->first();
+            if (empty($user->payment_password)) throw new Exception(lang("请先设置资金密码"));
+            //校验资金密码
+            if (!password_verify($params['safe_word'], $user->payment_password)) {
+                throw new Exception(lang('资金密码错误'));
+            }
+              
+            $serviceCharge = (new WithdrawService())->serviceCharge;
+            $amount = $params['amount'];
+            $address = $params['address'];
+            $real = bcsub($amount, $serviceCharge, 10);
+            $real = floatval($real);
+
+            if ($amount <= $serviceCharge) {
+                throw new Exception(lang("提现不能少于") . "{$serviceCharge} USDT");
+            }   
+
+            $wallet = WalletModel::where('member_id', $member_id)->first();
+            $temp = floatval($wallet->available_balance);
+            // 汇率
+            $rate = Config::where('field', 'exchange_rate_rmb')->first()->val ?? 1;
+            $exchange_rate_difference = Config::where('field', 'exchange_rate_difference')->first()->val ?? 0;
+            $rate = bcadd($rate, $exchange_rate_difference, 2);
+
+            $rate_usdt_amount = bcdiv($temp, $rate, 2);  // 钱包可用余额 折合USDT
+            $rate_rmb_amount = bcmul($amount, $rate, 2);  // 提现金额 折合RMB
+            if ($amount > $rate_usdt_amount) {
+                throw new Exception(lang("余额不足") . "{$serviceCharge} USDT");
+            }
+            $wallet = WalletModel::where('member_id', $member_id)->first();
+            $changeAmount = bcmul(($amount * -1), $rate, 2);
+            $beforeBalance = $wallet->available_balance;
+            $afterBalance = bcsub($wallet->available_balance, $rate_rmb_amount, 2);
+            $wallet->available_balance = $afterBalance;
+            $wallet->save();
+
+            $withdraw = Withdraw::create([
+                'member_id' => $member_id,
+                'amount' => $amount,
+                'service_charge' => $serviceCharge,
+                'to_account' => $real,
+                'address' => $address,
+                'exchange_rate' => $rate,
+                'status' => 0,
+                'after_balance' => $afterBalance
+            ]);
+            BalanceLogService::addLog($member_id, $changeAmount, $beforeBalance, $afterBalance, '提现', $withdraw->id, '');
+            return $this->success($withdraw,'提交成功');
+            
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+
+    }
+    
+
+    /**
+     * 提现(手动到账): DF001 支付宝转卡; DF002 支付宝转支付宝; DF005数字人民币
+     */
+    public function payout() {
+        try {
+            $params = request()->validate([
+                'amount' => ['required', 'numeric', 'min:0.01'],
+                'channel' => ['required', 'string', 'in:DF001,DF002,DF005'],
+                'bank_name' => ['required', 'string'],
+                'account' => ['required', 'string'],
+                'card_no' => ['required', 'string'],
+                'safe_word' => ['required'],
+            ]);
+            $member_id = request()->user->member_id;
+            $user = User::where('member_id', $member_id)->first();
+            if (empty($user->payment_password)) throw new Exception(lang("请先设置资金密码"));
+            //校验资金密码
+            if (!password_verify($params['safe_word'], $user->payment_password)) {
+                throw new Exception(lang('资金密码错误'));
+            }
+
+            $res = QianBaoWithdrawService::createOrder($member_id, $params['amount'], $params['channel'], $params['bank_name'], $params['account'], $params['card_no']);
+            if ($res['code'] == 0) { 
+                return $this->success($res,'提交成功');
+            }
+            return $this->error($res['text']);
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+    /**
+     * 提现(自动到账): DF001 支付宝转卡/DF002 支付宝转支付宝
+     */
+    public function autoPayout()
+    {
+        try {
+            $params = request()->validate([
+                'amount' => ['required', 'numeric', 'min:0.01'],
+                'channel' => ['required', 'string'],
+                'bank_name' => ['required', 'string'],
+                'account' => ['required', 'string'],
+                'card_no' => ['required', 'string'],
+            ]);
+            $member_id = request()->user->member_id;
+            $res = PaymentOrderService::autoCreatePayout($member_id, $params['amount'], $params['channel'], $params['bank_name'], $params['account'], $params['card_no']);
+            if (empty($res['text'])) { 
+                return $this->success($res,'提交成功');
+            }
+            return $this->error($res['text']);
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+    public function addBank()
+    {
+        try {
+            $params = request()->validate([
+                'id' => 'nullable|integer',
+                'channel' => 'required',
+                'account' => 'required',
+                'card_no' => 'required',
+                'bank_name' => 'required',
+                'alias' => 'nullable',
+            ]);
+            $member_id = request()->user->member_id;
+            if (!empty($params['id'])) {
+                $info = Bank::where('id', $params['id'])->where('member_id', $member_id)->first();
+                if (empty($info)) throw new Exception(lang('找不到此记录'));
+                $info->channel = $params['channel'];
+                $info->account = $params['account'];
+                $info->card_no = $params['card_no'];
+                $info->bank_name = $params['bank_name'];
+                $info->alias = $params['alias'] ?? '';
+                $info->save();
+            } else {
+                $count = Bank::where('member_id', $member_id)->where('channel', $params['channel'])->count();
+                if ($count >= 5) throw new Exception(lang('已达添加上限'));
+                Bank::create([
+                    'member_id' => $member_id,
+                    'channel' => $params['channel'],
+                    'account' => $params['account'],
+                    'card_no' => $params['card_no'],
+                    'bank_name' => $params['bank_name'],
+                    'alias' => $params['alias'] ?? '',
+                ]);
+            }
+
+            return $this->success([],'提交成功');
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+    public function delBank()
+    {
+        try {
+            $params = request()->validate([
+                'id' => 'required|integer',
+            ]);
+            $member_id = request()->user->member_id;
+            $info = Bank::where('id', $params['id'])->where('member_id', $member_id)->first();
+            if (empty($info)) throw new Exception(lang('找不到此记录'));
+               
+            $info->delete();
+            return $this->success([],'删除成功');
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+    public function bankList()
+    {
+        try {
+            $params = request()->validate([
+                'channel' => 'nullable',
+            ]);
+            $member_id = request()->user->member_id;
+            $where = !empty($params['channel']) ? ['channel' => $params['channel']] : [];
+            $list = Bank::where('member_id', $member_id)->where($where)->get()->toArray();
+
+            return $this->success([
+                'list' => $list,
+            ]);
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+    public function addAddress()
+    {
+        try {
+            $params = request()->validate([
+                'id' => 'nullable|integer',
+                'address' => 'required',
+                'alias' => 'nullable',
+            ]);
+            $member_id = request()->user->member_id;
+            if (!empty($params['id'])) {
+                $info = Address::where('id', $params['id'])->where('member_id', $member_id)->first();
+                if (empty($info)) throw new Exception(lang('找不到此记录'));
+                $info->address = $params['address'];
+                $info->alias = $params['alias'] ?? '';
+                $info->save();
+            } else {
+                $count = Address::where('member_id', $member_id)->where('address', $params['address'])->count();
+                if ($count >= 5) throw new Exception(lang('已达添加上限'));
+                Address::create([
+                    'member_id' => $member_id,
+                    'address' => $params['address'],
+                    'alias' => $params['alias'] ?? '',
+                ]);
+            }
+
+            return $this->success([],'提交成功');
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+    public function delAddress()
+    {
+        try {
+            $params = request()->validate([
+                'id' => 'required|integer',
+            ]);
+            $member_id = request()->user->member_id;
+            $info = Address::where('id', $params['id'])->where('member_id', $member_id)->first();
+            if (empty($info)) throw new Exception(lang('找不到此记录'));
+               
+            $info->delete();
+            return $this->success([],'删除成功');
+        } catch (ValidationException $e) {
+            return $this->error($e->validator->errors()->first());
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+    public function address()
+    {
+        try {
+            $member_id = request()->user->member_id;
+            $list = Address::where('member_id', $member_id)->get()->toArray();
+
+            return $this->success([
+                'list' => $list,
+            ]);
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+    }
+
+}

+ 2 - 0
app/Http/Kernel.php

@@ -67,6 +67,8 @@ class Kernel extends HttpKernel
         'admin.jwt' => \App\Http\Middleware\JwtAdminMiddleware::class,
         'jwt' => \App\Http\Middleware\JwtMiddleware::class,
         'check.button.uri' => \App\Http\Middleware\CheckButtonPermission::class,
+        'check.token' => \App\Http\Middleware\CheckToken::class,
+
         // 系统默认的中间件
         'auth' => Authenticate::class,
         'auth.basic' => AuthenticateWithBasicAuth::class,

+ 62 - 0
app/Http/Middleware/CheckToken.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use App\Models\User;
+use App\Models\UserSession;
+use Closure;
+use Illuminate\Http\Request;
+
+class CheckToken
+{
+    /**
+     * 处理传入请求
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        // 1. 从请求头获取 token(header 名称:token / Token 都支持)
+        $token = $request->header('authorization');
+
+        // 2. 如果请求头没有 token,直接返回未授权
+        if (empty($token)) {
+            return response()->json([
+                'code' => 401,
+                'msg' => lang('请携带token访问')
+            ], 401);
+        }
+
+        // 3. 查询 user_session:token 存在 + 未过期
+        $session = UserSession::where('token', $token)
+            ->where('expire_time', '>', now()) // 过期时间 > 当前时间 = 未过期
+            ->first();
+
+        // 4. 会话不存在或已过期
+        if (!$session) {
+            return response()->json([
+                'code' => 401,
+                'msg' => lang('token无效或已过期')
+            ], 401);
+        }
+
+        // 5. 查询对应用户信息
+        $user = User::where('user_id',$session->user_id)->first();
+
+        // 6. 用户不存在
+        if (!$user) {
+            return response()->json([
+                'code' => 401,
+                'msg' => lang('用户不存在')
+            ], 401);
+        }
+
+        // 7. 把用户信息赋值给 $request->user
+        $request->user = $user;
+
+        // 8. 放行请求,进入控制器
+        return $next($request);
+    }
+}

+ 1 - 1
app/Models/ActivityReward.php

@@ -20,7 +20,7 @@ class ActivityReward extends BaseModel
     const STATUS_DOWN = 0;
 
     protected $table = 'activity_rewards';
-    protected $fillable = ['title', 'sub_title', 'detail_image', 'start_time', 'end_time', 'status'];
+    protected $fillable = ['title', 'sub_title', 'detail_image', 'start_time', 'end_time', 'status','type','lang','content', 'params','pc_image'];
 
     public function activityUser(): HasMany
     {

+ 1 - 1
app/Models/BalanceLog.php

@@ -5,7 +5,7 @@ namespace App\Models;
 class BalanceLog extends BaseModel
 {
     protected $table = 'balance_logs';
-    protected $fillable = ['room_id', 'member_id', 'amount', 'before_balance', 'after_balance', 'change_type', 'remark', 'related_id'];
+    protected $fillable = ['room_id', 'member_id', 'amount', 'before_balance', 'after_balance', 'change_type', 'remark', 'related_id','type','frozen_status'];
     protected $hidden = [ 'updated_at'];
 
     function member()

+ 8 - 0
app/Models/Banner.php

@@ -0,0 +1,8 @@
+<?php
+namespace App\Models;
+
+class Banner extends BaseModel
+{
+    protected $table = 'banner';
+    protected $fillable = ['title', 'image', 'link' ,'type' ,'sort' ,'cny_rate','lang' ];
+}

+ 12 - 0
app/Models/FundsRecord.php

@@ -0,0 +1,12 @@
+<?php
+namespace App\Models;
+class FundsRecord extends BaseModel
+{
+    protected $table = 'balance_logs';
+    protected $fillable = ['id', 'room_id', 'member_id' ,'amount' ,'before_balance' ,'after_balance' ,'change_type','created_at','remark'];
+
+    public static function addData($data)
+    {
+        return self::create($data);
+    }
+}

+ 10 - 0
app/Models/Level.php

@@ -0,0 +1,10 @@
+<?php
+namespace App\Models;
+
+class Level extends BaseModel
+{
+    protected $table = 'level';
+    protected $fillable = ['level', 'level_name', 'img', 'recharge'];
+    protected $hidden = [];
+    
+}

+ 13 - 0
app/Models/LhcLottery.php

@@ -0,0 +1,13 @@
+<?php
+namespace App\Models;
+
+class LhcLottery extends BaseModel
+{
+    protected $table = 'lhc_lottery';
+    protected $fillable = ['issue', 'open_code','open_time','is_settlement','next_open_time'];
+
+    protected function getNextOpenTimeAttribute($value): string
+    {
+        return $value ? date('Y-m-d H:i:s', $value) : '';
+    }
+}

+ 9 - 0
app/Models/LhcNumber.php

@@ -0,0 +1,9 @@
+<?php
+namespace App\Models;
+
+class LhcNumber extends BaseModel
+{
+    protected $table = 'lhc_number';
+    protected $fillable = ['game','gameplay','number','odds','updated_by'];
+
+}

+ 14 - 0
app/Models/LhcOrder.php

@@ -0,0 +1,14 @@
+<?php
+namespace App\Models;
+
+class LhcOrder extends BaseModel
+{
+    protected $table = 'lhc_order';
+    protected $fillable = ['issue','ordernum','member_id','game','gameplay','number','odds','amount','lottery_status','win_amount','profit_and_loss','is_faker','remark','type'];
+    protected $hidden = [];
+    
+    public $timestamps = true;
+    protected $dateFormat = 'U'; // U 代表 UNIX 时间戳(int)
+
+ 
+}

+ 30 - 0
app/Models/Operation.php

@@ -0,0 +1,30 @@
+<?php
+
+
+namespace App\Models;
+
+
+// // 关键:导入正确的 Builder 类(Eloquent 构建器)
+// use Illuminate\Database\Eloquent\Builder;
+
+class Operation extends BaseModel
+{
+    protected $table = 'operation';
+    protected $fillable = ['date', 'recharge', 'withdraw', 'balance_difference', 'total_price', 'user_total_money'];
+    
+    
+    // public function newQuery($excludeDeleted = true): Builder
+    // {
+    //     // 1. 获取原生 Eloquent 查询构建器
+    //     $query = parent::newQuery($excludeDeleted);
+        
+    //     // 2. 强制清空当前连接的表前缀(从根源阻止拼接)
+    //     $this->getConnection()->setTablePrefix('');
+        
+    //     // 3. 强制指定查询的表名为 la_operation(覆盖所有拼接逻辑)
+    //     $query->from('bot_operation');
+        
+    //     return $query;
+    // }
+}
+

+ 42 - 0
app/Models/Order.php

@@ -0,0 +1,42 @@
+<?php
+namespace App\Models;
+use Carbon\Carbon;
+
+class Order extends BaseModel
+{
+    protected $table = 'sport_order';
+    protected $fillable = ['user_id', 'order_id', 'issue' ,'is_roll' ,'amount' ,'cny_rate' ,'win_amount','profit_and_loss','is_faker','is_win', 'stattus','settlement_status',
+            'pay_status','pay_time','pay_type','return_status','return_apply_time','return_operation_time','failure_msg', 'create_time','remark','settlement_time' ];
+    
+    public $timestamps = true;
+    protected $dateFormat = 'U'; // U 代表 UNIX 时间戳(int)
+
+    // 2. 修改创建时间字段名为 create_time
+    const CREATED_AT = 'create_time';
+    const UPDATED_AT = 'update_time';
+
+    protected function getPayTimeAttribute($value): string
+    {
+        return $value ? date('Y-m-d H:i:s', $value) : '';
+    }
+
+    protected function getReturnApplyTimeAttribute($value): string
+    {
+        return $value ? date('Y-m-d H:i:s', $value) : '';
+    }
+    
+    protected function getReturnOperationTimeAttribute($value): string
+    {
+        return $value ? date('Y-m-d H:i:s', $value) : '';
+    }
+    
+    protected function getCreateTimeAttribute($value): string
+    {
+        return Carbon::parse($value)->timezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getSettlementTimeAttribute($value): string
+    {
+        return $value ? date('Y-m-d H:i:s', $value) : '';
+    }
+}

+ 184 - 0
app/Models/Sport.php

@@ -0,0 +1,184 @@
+<?php
+
+namespace App\Models;
+use App\Models\SportLeague;
+use App\Models\SportTeam;
+use Illuminate\Support\Facades\Cache;
+
+
+class Sport extends BaseModel
+{
+    protected $table = 'sport';
+    protected $fillable = ['data_id', 'home_team_id', 'home_team_en', 'home_team', 'home_team_logo', 'guest_team_id', 'guest_team_en', 'guest_team', 'guest_team_logo', 'half_score', 'rbt', 
+    'is_roll', 'score', 'league_en','league','odds','state','game_time','status','handicap_limit','over_under_limit','duying_limit','correct_core_limit','odd_even_limit','total_goal_limit',
+    'is_handicap', 'is_over_under','is_duying','is_correct_core','is_odd_even','is_total_goal','is_locked','fixture_status','is_send','odd_ids_locked','refund_status','odd_fixture_status',
+    'error','odd_values_locked','remark','is_rec'];
+    protected $hidden = [];
+    
+    public static function getLongStatus($status){
+        $long_status = [
+            'Time To Be Defined' => '时间待定',
+            'Not Started' => '未开赛',
+            'First Half' => '上半场',
+            'First Half, Kick Off' => '上半场,开球',
+            'Halftime' => '中场休息',
+            'Second Half' => '下半场',
+            'Second Half, 2nd Half Started' => '下半场,已开球',
+            'Extra Time' => '加时赛',
+            'Break Time' => '休息时间(常规赛与加时赛之间)',
+            'Penalty In Progress' => '点球大战进行中',
+            'Match Suspended' => '比赛暂停',
+            'Match Interrupted' => '比赛中断',
+            'Match Finished' => '比赛结束',
+            'Match Postponed' => '比赛延期',
+            'Match Cancelled' => '比赛取消',
+            'Match Abandoned' => '比赛腰斩(废弃)',
+            'Technical Loss' => '技术性判负',
+            'WalkOver' => '弃权/退赛(对手直接晋级)',
+            'In Progress' => '进行中',
+        ];
+        return $long_status[$status] ?? $status;
+    }
+
+    public static function addSportTeam($sport_data){
+        if (!empty($sport_data['home_team_id'])) {
+            $info = SportTeam::where('team_id', $sport_data['home_team_id'])->first();
+            if (!$info) { 
+                SportTeam::create([
+                    'team_id' => $sport_data['home_team_id'],
+                    'team_name_en' => $sport_data['home_team'],
+                    'logo' => $sport_data['home_team_logo'],
+                ]);
+            }
+        } 
+        if (!empty($sport_data['guest_team_id'])) {
+            $info = SportTeam::where('team_id', $sport_data['guest_team_id'])->first();
+            if (!$info) { 
+                SportTeam::create([
+                    'team_id' => $sport_data['guest_team_id'],
+                    'team_name_en' => $sport_data['guest_team'],
+                    'logo' => $sport_data['guest_team_logo'],
+                ]);
+            }
+        }
+        return true;
+    }
+
+    public static function addSportLeague($league){
+        if (!empty($league['name'])) {
+            $info = SportLeague::where('league_id', $league['id'])->first();
+            if (!$info) {
+                $info = SportLeague::where('league_en', $league['name'])->first();
+                if ($info) {
+                    $info->league_id = $league['id'] ?? '';
+                    $info->logo = $league['logo'] ?? '';
+                    $info->country = $league['country'] ?? '';
+                    $info->season = $league['season'] ?? '';
+                    $info->round = $league['round'] ?? '';
+                    $info->standings = !empty($league['standings']) ? 1 : (isset($league['standings']) ? 0 : null);
+                    $info->save();
+                }
+            }
+            if (!$info) { 
+                SportLeague::create([
+                    'league_en' => $league['name'],
+                    'logo' => $league['logo'] ?? '',
+                    'league_id' => $league['id'] ?? '',
+                    'country' => $league['country'] ?? '',
+                    'season' => $league['season'] ?? '',
+                    'round' => $league['round'] ?? '',
+                    'standings' => !empty($league['standings']) ? 1 : (isset($league['standings']) ? 0 : null),
+                ]);
+            }
+        } 
+        return true;
+    }
+
+    //翻译赔率
+    public static function doOdds($odds, $odd_values_locked = []) {
+        // 1. 获取赔率,缓存数据
+        $sport_odds = cache('sport_odds');
+        if (!$sport_odds) {
+            $sport_odds = SportOdds::where('function_name', '<>', null)->get()->toArray();
+            Cache::set('sport_odds', $sport_odds, 300); //有效期5分钟
+        }
+
+        $sport_odds = array_column($sport_odds, null,'odd_name_en');
+        $new_odds = [];
+        foreach($odds as $k => $item) {
+            foreach($item['values'] as &$values) {
+                if ($values['value'] == 'Over 1.5') {
+                    $values['value'] = '1.5 or more';
+                }
+                $special_str = self::isSpecialStr($values['value']);
+                if ($special_str) {
+                    $values['value_text'] = lang($special_str, [str_replace($special_str,'', $values['value'])]);
+                } else {
+                    $values['value_text'] = lang($values['value']);
+                }
+
+                //判断下注项是否加锁
+                $odd_id = $item['id'];
+                $handicap = isset($values['handicap']) ? $values['handicap'] : "";
+                $vh = $values['value']."_".$handicap;
+                if ($odd_values_locked && isset($odd_values_locked[$odd_id]) && array_search($vh, $odd_values_locked[$odd_id]) !== false) {
+                    $values['is_locked'] = 1;
+                } else {
+                    $values['is_locked'] = 0;
+                }
+            }
+            $item['name_en'] = $item['name'];
+            $item['name'] = isset($sport_odds[$item['name']]) ? $sport_odds[$item['name']]['odd_name'] : $item['name'];
+            $new_odds[] = $item;
+        }
+        return $new_odds;
+    }
+
+    public static function isSpecialStr($value) {
+        
+        if (stripos($value, 'u/yes ') !== false) {
+            return 'u/yes ';
+        } elseif (stripos($value, 'u/no ') !== false) {
+            return 'u/no ';
+        } elseif (stripos($value, 'o/yes ') !== false) {
+            return 'o/yes ';
+        } elseif (stripos($value, 'o/no ') !== false) {
+            return 'o/no ';
+        } elseif (stripos($value, ' or more') !== false) {
+            return ' or more';
+        } elseif (stripos($value, 'more ') !== false) {
+            return 'more ';
+        } elseif (stripos($value, 'Draw/Over ') !== false) {
+            return 'Draw/Over ';
+        } elseif (stripos($value, 'Away/Over ') !== false) {
+            return 'Away/Over ';
+        } elseif (stripos($value, 'Home/Over ') !== false) {
+            return 'Home/Over ';
+        } elseif (stripos($value, 'Draw/Under ') !== false) {
+            return 'Draw/Under ';
+        } elseif (stripos($value, 'Away/Under ') !== false) {
+            return 'Away/Under ';
+        } elseif (stripos($value, 'Home/Under ') !== false) {
+            return 'Home/Under ';
+        } elseif (stripos($value, 'Over ') !== false) {
+            return 'Over ';
+        } elseif (stripos($value, 'Under ') !== false) {
+            return 'Under ';
+        }
+        return '';
+    }
+
+    protected function getOddValuesLockedAttribute($value)
+    {
+        return $value ? json_decode($value, true) : [];
+    }
+    protected function getOddIdsLockedAttribute($value)
+    {
+        return $value ? json_decode($value, true) : [];
+    }
+
+    protected function getLeagueDataAttribute($value)
+    {
+        return $value ? json_decode($value, true) : [];
+    }
+}

+ 9 - 0
app/Models/SportEvent.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class SportEvent extends BaseModel
+{
+    protected $table = 'sport_event';
+    protected $fillable = ['data_id', 'type', 'time_elapsed', 'team_id', 'time', 'player', 'detail', 'comments', 'assist'];
+}

+ 19 - 0
app/Models/SportLeague.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Models;
+
+class SportLeague extends BaseModel
+{
+    protected $table = 'sport_league';
+    protected $fillable = ['league_id', 'league_en', 'league', 'logo', 'status','country','season','round','standings'];
+    public $timestamps = true;
+    protected $dateFormat = 'U'; // U 代表 UNIX 时间戳(int)
+
+    // 2. 修改创建时间字段名为 create_time
+    const CREATED_AT = 'create_time';
+    const UPDATED_AT = 'update_time';
+
+    public static function getLeagueName($league_id){
+        return self::where(['league_id' => $league_id])->value('league');
+    }
+}

+ 9 - 0
app/Models/SportOdds.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class SportOdds extends BaseModel
+{
+    protected $table = 'sport_odds';
+    protected $fillable = ['odd_id', 'odd_name_en', 'odd_name', 'function_name'];
+}

+ 10 - 0
app/Models/SportStatistics.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace App\Models;
+
+class SportStatistics extends BaseModel
+{
+    protected $table = 'sport_statistics';
+    protected $fillable = ['data_id', 'team_id', 'half', 'shots_on_goal', 'shots_off_goal', 'shots_insidebox', 'shots_outsidebox', 'total_shots', 'blocked_shots', 'fouls', 'corner_kicks', 'offsides', 'ball_possession', 'yellow_cards', 'red_cards', 'goalkeeper_saves', 'total_passes', 'passes_accurate', 'passes_percent'];
+
+}

+ 19 - 0
app/Models/SportTeam.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Models;
+
+class SportTeam extends BaseModel
+{
+    protected $table = 'sport_team';
+    protected $fillable = ['team_id', 'team_name_en', 'team_name', 'logo', 'status'];
+    public $timestamps = true;
+    protected $dateFormat = 'U'; // U 代表 UNIX 时间戳(int)
+
+    // 2. 修改创建时间字段名为 create_time
+    const CREATED_AT = 'create_time';
+    const UPDATED_AT = 'update_time';
+
+    public static function getTeamName($team_id){
+        return self::where(['team_id' => $team_id])->value('team_name');
+    }
+}

+ 32 - 5
app/Models/User.php

@@ -2,6 +2,9 @@
 
 namespace App\Models;
 
+// // 关键:导入正确的 Builder 类(Eloquent 构建器)
+// use Illuminate\Database\Eloquent\Builder;
+
 use Carbon\Carbon;
 
 /**
@@ -20,16 +23,31 @@ use Carbon\Carbon;
 class User extends BaseModel
 {
     protected $table = 'users';
-    protected $fillable = ['usdt', 'is_banned', 'last_active_time', 'visitor_id', 'register_ip', 'status', 'admin_note', 'member_id', 'first_name', 'game_id', 'username', 'secret_key', 'secret_pass', 'language'];
+    protected $fillable = ['usdt', 'is_banned', 'last_active_time', 'visitor_id', 'register_ip', 'status', 'admin_note', 'member_id', 'first_name', 
+    'game_id', 'username', 'secret_key', 'secret_pass', 'language','user_code', 'agent_user_code','level'];
     protected $attributes = [
         'language' => 'zh',
     ];
     protected $hidden = ['updated_at'];
 
-
+    
+    // public function newQuery($excludeDeleted = true): Builder
+    // {
+    //     // 1. 获取原生 Eloquent 查询构建器
+    //     $query = parent::newQuery($excludeDeleted);
+        
+    //     // 2. 强制清空当前连接的表前缀(从根源阻止拼接)
+    //     $this->getConnection()->setTablePrefix('');
+        
+    //     // 3. 强制指定查询的表名为 la_operation(覆盖所有拼接逻辑)
+    //     $query->from('bot_users');
+        
+    //     return $query;
+    // }
+    
     function getLastActiveTimeAttribute($value): string
     {
-        if ($value > 0) {
+        if ($value > 0 && !is_numeric($value)) {
             return date('Y-m-d H:i', strtotime($value));
         }
         return "";
@@ -42,8 +60,17 @@ class User extends BaseModel
 
     public function wallet()
     {
-        return $this->belongsTo(Wallet::class, 'id', 'user_id')
-            ->select('id', 'user_id', 'member_id', 'address', 'available_balance');
+        return $this->belongsTo(Wallet::class, 'user_id', 'user_id');
+    }
+
+    public function level()
+    {
+        return $this->belongsTo(Level::class, 'level', 'level')->select('id', 'level', 'level_name','img','recharge');
+    }
+
+    public function agent()
+    {
+        return $this->belongsTo(User::class, 'agent_user_code', 'user_code')->select('id', 'user_code', 'account','first_name','member_id');
     }
 
     public function setLanguage($language): void

+ 25 - 0
app/Models/UserLogin.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Models;
+
+class UserLogin extends BaseModel
+{
+    protected $table = 'user_login';
+    protected $fillable = ['user_id', 'login_ip'];
+    protected $hidden = [];
+
+    // 获取用户未登录天数(最后第二次未登录的天数)
+    public static function getNotLoginDays($memberId)
+    {
+        $list = self::where('user_id', $memberId)->orderByDesc('id')->limit(2)->get()->toArray();
+        if (count($list) < 2) {
+            return 0;
+        }
+        if (date('Y-m-d', strtotime($list[0]['created_at'])) != date('Y-m-d')) {
+            return 0;
+        }
+        $diff = strtotime($list[0]['created_at']) - strtotime($list[1]['created_at']);
+        $days = ceil($diff / 86400);
+        return abs($days);
+    }
+}

+ 13 - 0
app/Models/UserSession.php

@@ -0,0 +1,13 @@
+<?php
+
+
+namespace App\Models;
+
+
+class UserSession extends BaseModel
+{
+    protected $table = 'user_session';
+    protected $fillable = ['user_id', 'token', 'expire_time'];
+    
+}
+

+ 1 - 1
app/Models/Wallet.php

@@ -4,5 +4,5 @@ namespace App\Models;
 class Wallet extends BaseModel
 {
     protected $table = 'wallets';
-    // protected $fillable = ['user_id','member_id', 'coin', 'available_balance','frozen_balance','total_balance'];
+    protected $fillable = ['user_id','member_id', 'coin', 'available_balance','frozen_balance','total_balance','yuebao', 'yuebao_invest'];
 }

+ 23 - 0
app/Models/WalletBonus.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Models;
+class WalletBonus extends BaseModel
+{
+    protected $table = 'wallet_bonus';
+    protected $fillable = ['member_id','activity_id','related_id','amount','need_flow','remaind_flow','status'];
+
+    public static function addData($memberId, $type, $bonusAmount, $activityId, $related_id, $need_flow)
+    {
+        $data = [
+            'member_id' => $memberId,
+            'type' => $type,    //类型1充值活动返彩;2余额宝收益;3老用户回归日充值返彩 
+            'activity_id' => $activityId,
+            'related_id' => $related_id,
+            'amount' => $bonusAmount,
+            'need_flow' => $need_flow,
+            'remaind_flow' => $need_flow,
+            'status' => 1,
+        ];
+        self::create($data);
+    }
+}

+ 14 - 0
app/Models/YuebaoItem.php

@@ -0,0 +1,14 @@
+<?php
+namespace App\Models;
+
+class YuebaoItem extends BaseModel
+{
+    protected $table = 'yuebao_item';
+    protected $fillable = ['user_id','principal','surplus_principal','daily_interest_rate','status'];
+    protected $hidden = [];
+    
+    public $timestamps = true;
+    protected $dateFormat = 'U'; // U 代表 UNIX 时间戳(int)
+
+ 
+}

+ 14 - 0
app/Models/YuebaoLog.php

@@ -0,0 +1,14 @@
+<?php
+namespace App\Models;
+
+class YuebaoLog extends BaseModel
+{
+    protected $table = 'yuebao_log';
+    protected $fillable = ['user_id','item_id','amount','type'];
+    protected $hidden = [];
+    
+    public $timestamps = true;
+    protected $dateFormat = 'U'; // U 代表 UNIX 时间戳(int)
+
+ 
+}

+ 1 - 1
app/Providers/RouteServiceProvider.php

@@ -51,7 +51,7 @@ class RouteServiceProvider extends ServiceProvider
     protected function configureRateLimiting()
     {
         RateLimiter::for('api', function (Request $request) {
-            return Limit::perMinute(60)->by($request->user() ? $request->user()->id : ($request->ip()));
+            return Limit::perMinute(200)->by($request->user() ? $request->user()->id : ($request->ip()));
         });
     }
 }

+ 4 - 0
app/Services/ActivityRewardService.php

@@ -36,6 +36,10 @@ class ActivityRewardService extends BaseService
             $where[] = ['status', '=', $search['status']];
         }
 
+        if (isset($search['type']) && $search['type'] != '') {
+            $where[] = ['type', '=', $search['type']];
+        }
+
         if (isset($search['title']) && !empty($search['title'])) {
             $where[] = ['title', 'like', "%{$search['title']}%"];
         }

+ 203 - 5
app/Services/BalanceLogService.php

@@ -2,7 +2,14 @@
 
 namespace App\Services;
 
+use App\Models\ActivityReward;
 use App\Models\BalanceLog;
+use App\Models\User;
+use App\Models\Config;
+use App\Models\WalletBonus;
+use App\Models\Wallet;
+use App\Models\Level;
+use App\Models\UserLogin;
 
 // 余额额变动记录
 class BalanceLogService extends BaseService
@@ -15,8 +22,11 @@ class BalanceLogService extends BaseService
     //全部类型
     public static array $RW = [
         '充值', '人工充值', '三方充值', '注册赠送', '优惠活动',
-        '提现', '人工扣款', '三方提现', '投注',
-        '中奖', '资产转移', '比比返', '返水', '回水', '笔笔返', '投注退分'
+        '提现', '人工扣款', '三方提现', 
+        '体彩投注','体彩退款','体彩和局退款','体彩中奖','体彩输半退款',
+        '澳门六合彩投注','澳门六合彩退款','澳门六合彩和局退款','澳门六合彩中奖',
+        '香港六合彩投注','香港六合彩退款','香港六合彩和局退款','香港六合彩中奖','PC28投注','极速28投注',
+        '投注','中奖', '资产转移', '比比返', '返水', '回水', '笔笔返', '投注退分','充值返现','即充即送','老用户回归','余额宝转入','转出至余额宝','余额宝利息','流水解冻'
     ];
 
     public static function init($telegram, $data, $chatId, $firstName, $messageId, $callbackId): void
@@ -64,6 +74,10 @@ class BalanceLogService extends BaseService
             $where[] = ['member_id', '=', $search['member_id']];
         }
 
+        if (isset($search['type']) && !empty($search['type'])) {
+            $where[] = ['type', '=', $search['type']];
+        }
+        
         if (isset($search['change_type']) && !empty($search['change_type'])) {
             $where[] = ['change_type', '=', $search['change_type']];
         }
@@ -131,8 +145,7 @@ class BalanceLogService extends BaseService
         if (in_array($change_type, static::$computeBackFlowChangeTypes)) {
             BackflowService::updateOrCreate((string)$memberId, floatval($amount));
         }
-
-
+        
         $data = [];
         $data['member_id'] = $memberId;
         $data['amount'] = $amount;
@@ -142,7 +155,99 @@ class BalanceLogService extends BaseService
         $data['related_id'] = $related_id;
         $data['remark'] = $remark;
         if ($room_id) $data['room_id'] = $room_id;
-        return static::$MODEL::create($data);
+        $result = static::$MODEL::create($data);
+
+        //充值返现活动
+        if ( in_array($change_type, ['充值','人工充值','三方充值'])) {
+            $user = User::where('member_id', $memberId)->first();
+            //充值更新用户等级
+            $total_recharge = self::getTotalRecharge($memberId);
+            $total_recharge = bcadd($total_recharge, $amount, 2);
+            $level = self::calculateLevel($total_recharge);
+            if ($level > $user->level) {
+                $user->level = $level;
+                $user->save();
+            }
+
+            $remark_amount = bcadd($amount, 0, 2);
+            //返现比例(给邀请人返现)
+            $rate = Config::where('field', 'recharge_rate')->first()->val ?? 0;
+            $add_amount = bcmul($amount, $rate/100, 2);
+            if ($add_amount > 0) {
+                //被邀请人每次充值,都给邀请人返现,直接可用余额
+                $agent_user_code = $user->agent_user_code;
+                if (!empty($agent_user_code)) {
+                    $agent_member_id = User::where('user_code', $agent_user_code)->first()->member_id;
+                    if (!empty($agent_member_id)) {
+                        $balanceData = WalletService::updateBalance($agent_member_id, $add_amount);
+                        BalanceLogService::addLog($agent_member_id, $add_amount, $balanceData['before_balance'], $balanceData['after_balance'], '充值返现', $related_id, '被邀请人:'.$memberId."充值金额为:{$remark_amount}");
+                    }
+                }
+            }
+
+            $walletInfo = Wallet::where('member_id', $memberId)->first();
+            //即充即送-返彩活动
+            $bonusAmount = self::calculateRechargeBonus($amount,$memberId, $related_id);
+            if ($bonusAmount > 0) {
+                static::$MODEL::create([
+                    'type' => 2,
+                    'member_id' => $memberId,
+                    'amount' => $bonusAmount,
+                    'before_balance' => $walletInfo->frozen_balance,
+                    'after_balance' => bcadd($walletInfo->frozen_balance, $bonusAmount, 2),
+                    'change_type' => '即充即送',
+                    'frozen_status' => 1,
+                    'related_id' => $related_id,
+                    'remark' => '充值金额为:'.$remark_amount,
+                ]);
+                $walletInfo->frozen_balance = bcadd($walletInfo->frozen_balance, $bonusAmount, 2);
+            }
+
+            //老用户回归-返彩活动
+            $bonusAmount = self::calculateUserReturnRechargeBonus($amount,$memberId, $level, $related_id);
+            
+            if ($bonusAmount > 0) {
+                static::$MODEL::create([
+                    'type' => 2,
+                    'member_id' => $memberId,
+                    'amount' => $bonusAmount,
+                    'before_balance' => $walletInfo->frozen_balance,
+                    'after_balance' => bcadd($walletInfo->frozen_balance, $bonusAmount, 2),
+                    'change_type' => '老用户回归',
+                    'frozen_status' => 1,
+                    'related_id' => $related_id,
+                    'remark' => '充值金额为:'.$remark_amount,
+                ]);
+                $walletInfo->frozen_balance = bcadd($walletInfo->frozen_balance, $bonusAmount, 2);
+            }
+            $walletInfo->save();
+        }
+        
+        return $result;
+    }
+
+    /**
+     * 获取用户累计充值金额
+     */
+    public static function getTotalRecharge($memberId): float
+    {
+        return static::$MODEL::where('member_id', $memberId)->where('type',1)->whereIn('change_type', ['充值','人工充值', '三方充值'])->sum('amount');
+    }
+
+    /**
+     * 根据历史累计充值金额计算等级
+     * @param float $totalAmount 历史累计充值金额
+     * @return int 等级(1-5)
+     */
+    public static function calculateLevel(float $totalAmount): int
+    {
+        $levelList = Level::orderBy('recharge', 'desc')->get()->toArray();
+        foreach ($levelList as $item) {
+            if ($totalAmount >= $item['recharge']) {
+                return $item['level'];
+            }
+        }
+        return 0; 
     }
 
     /**
@@ -250,4 +355,97 @@ class BalanceLogService extends BaseService
         ];
     }
 
+    /**
+     * 计算充值的返现金额
+     * @param float $amount 充值金额(人民币)
+     */
+    public static function calculateRechargeBonus($amount,$memberId, $related_id = null)
+    {
+        $activity = ActivityReward::where('lang','zh')->where('type',3)->where('status',1)->where('start_time', '<', time())->where('end_time', '>', time())->first();
+        if (!$activity) {
+            return false;
+        }
+        $params = $activity->params ? json_decode($activity->params, true) : [];
+        if (!$params || empty($params['reward_ratio'])) {
+            return false;
+        }
+        // 规则1:单笔最低金额
+        if (isset($params['min_recharge_amount']) && $amount < $params['min_recharge_amount']) {
+            return false;
+        }
+
+        // 规则2:必须是每日前几笔充值
+        if (!empty($params['reward_limit_count'])) {
+            //今日已充值数
+            $dailyOrder = static::model()::where('member_id', $memberId)
+                ->whereBetween('created_at', [date('Y-m-d 00:00:00'), date('Y-m-d 23:59:59')])
+                ->whereIn('change_type', ['充值','三方充值'])
+                ->count();
+            if ($dailyOrder > $params['reward_limit_count']) {
+                return false;
+            }
+        }
+
+        // 规则3:返彩比例,不超过单笔最高10000元
+        $rate = bcdiv($params['reward_ratio'], 100, 6); // 0.5%
+        $maxBonus = $params['max_reward_amount'] ?? 10000;  // 单笔最高上限
+        $calculatedBonus = bcmul($amount, $rate, 2); // 保留两位小数
+        $bonusAmount = min($calculatedBonus, $maxBonus);
+
+        //倍率流水
+        $need_flow = !empty($params['reward_turnover_ratio']) ? bcmul($bonusAmount, $params['reward_turnover_ratio'], 2) : $bonusAmount;
+        
+        //返彩流水
+        WalletBonus::addData($memberId, 1, $bonusAmount, $activity->id, $related_id, $need_flow);
+
+        return $bonusAmount;
+    }
+
+    /**
+     * 计算老用户回归当日的充值返现金额
+     * @param float $amount 充值金额(人民币)
+     */
+    public static function calculateUserReturnRechargeBonus($amount,$memberId, $level, $related_id = null)
+    {
+        $activity = ActivityReward::where('lang','zh')->where('type',4)->where('status',1)->where('start_time', '<', time())->where('end_time', '>', time())->first();
+        if (!$activity) {
+            return false;
+        }
+        $params = $activity->params ? json_decode($activity->params, true) : [];
+        if (!$params || empty($params['no_login_days']) || empty($params['reward_rules'])) {
+            return false;
+        }
+
+        // 规则1:单笔最低金额
+        if (isset($params['min_amount']) && $amount < $params['min_amount']) {
+            return false;
+        }
+
+        // 规则2:多少天未登录
+        $noLoginDays = UserLogin::getNotLoginDays($memberId);
+        
+        if ($noLoginDays < $params['no_login_days']) {
+            return false;
+        }
+        
+        foreach($params['reward_rules'] as $rule) {
+            if (isset($rule['start_level']) && isset($rule['end_level'])) {
+                if ($level >= $rule['start_level'] && $level <= $rule['end_level']) {
+                    $bonusAmount = $rule['bonus'];
+                    //倍率流水
+                    $need_flow = !empty($rule['turnover_rate']) ? bcmul($bonusAmount, $rule['turnover_rate'], 2) : $bonusAmount;
+                    break;
+                }
+            }
+        }
+        if (!isset($bonusAmount)) {
+            return false;
+        }
+        
+        //返彩流水
+        WalletBonus::addData($memberId, 3, $bonusAmount, $activity->id, $related_id, $need_flow);
+
+        return $bonusAmount;
+    }
+
 }

+ 10 - 5
app/Services/BetService.php

@@ -1094,7 +1094,8 @@ class BetService extends BaseService
                 App::setLocale($lang);
             }
 
-            if ($v['is_send']) {
+            //根据环境变量配置是否发送电报
+            if ($v['is_send'] && env('SEND_TELEGRAM')) {
                 $language = User::where('member_id', $v['member_id'])->first()->language;
                 $wallet = WalletService::findOne(['member_id' => $v['member_id']]);
                 App::setLocale($language);
@@ -1121,10 +1122,14 @@ class BetService extends BaseService
         }
 
         $inlineButton = self::getOperateButton();
-        // 群通知
-        $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
-        if (($pc28Switch == 0 && is_numeric($issue_no)) || $pc28Switch == 1 && !is_numeric($issue_no)) {
-            SendTelegramGroupMessageJob::dispatch($text, $inlineButton, '', false, '--------------------------------');
+        
+        //根据环境变量配置是否发送电报
+        if (env('SEND_TELEGRAM')) {
+            // 群通知
+            $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
+            if (($pc28Switch == 0 && is_numeric($issue_no)) || $pc28Switch == 1 && !is_numeric($issue_no)) {
+                SendTelegramGroupMessageJob::dispatch($text, $inlineButton, '', false, '--------------------------------');
+            }
         }
 
 

+ 6 - 0
app/Services/IssueService.php

@@ -90,6 +90,12 @@ class IssueService extends BaseService
             $where[] = ['end_time', '<', date('Y-m-d H:i:s', time() - 1800)];
             $where[] = ['status', '!=', self::model()::STATUS_DRAW];
         }
+        if (!empty($search['start_time'])) {
+            $where[] = ['end_time', '>=', $search['start_time'].' 00:00:00'];
+        }
+        if (!empty($search['end_time'])) {
+            $where[] = ['end_time', '<=', $search['end_time'].' 23:59:59'];
+        }
         return $where;
     }
 

+ 9 - 0
app/Services/Payment/QianBaoService.php

@@ -26,6 +26,15 @@ class QianBaoService extends BaseService
         'ZFB001' => [10,100]
     ];
 
+    public static function withdrawChannel()
+    {
+        return [
+            self::ALIPAY_TO_CARD => lang('银行卡'),
+            self::ALIPAY_TO_ALIPAY => lang('支付宝'),
+            self::NUMBER_RMB => lang('数字人民币'),
+        ];
+    }
+
     // 获取异步的通知地址
     public static function getNotifyUrl()
     {

+ 5 - 0
app/Services/PaymentOrderService.php

@@ -176,6 +176,8 @@ class PaymentOrderService extends BaseService
     {
         $result = [];
         $result['chat_id'] = $memberId;
+        $result['code'] = 0;
+        $result['url'] = '';
         $channel = ''; // 支付的通道
         $product = SanJinService::$PRODUCT;
         $max = 0;
@@ -226,6 +228,7 @@ class PaymentOrderService extends BaseService
             if ($geText) {
                 $result['text'] = $geText;
             }
+            $result['code'] = 20001;
             return $result;
         }
 
@@ -262,8 +265,10 @@ class PaymentOrderService extends BaseService
             $text .= "请按实际支付金额进行付款,否则影响到账 \n";
             $text .= "支付完成后请耐心等待,支付到账会第一时间通知您! \n";
             $result['text'] = $text;
+            $result['url'] = $ret['data']['payUrl'];
         } else {
             $result['text'] = $ret['message'];
+            $result['code'] = 20002;
         }
         return $result;
     }

+ 7 - 3
app/Services/PcIssueService.php

@@ -226,10 +226,14 @@ class PcIssueService extends BaseService
                 if ($pc28Switch == 1) SendTelegramGroupMessageJob::dispatch($text, $buttons, $image, true);
             }
 
-            $recordImage = self::lotteryImage($info->issue_no);
-            if ($recordImage) {
-                if ($pc28Switch == 1) SendTelegramGroupMessageJob::dispatch('', [], url($recordImage), false);
+            //开发环境不生成开奖图片
+            if (env('APP_ENV') != 'local') {
+                $recordImage = self::lotteryImage($info->issue_no);
+                if ($recordImage) {
+                    if ($pc28Switch == 1) SendTelegramGroupMessageJob::dispatch('', [], url($recordImage), false);
+                }
             }
+            
             //中奖结算 并发送群通知 xxxx期开奖结果
             BetService::betSettled($info->issue_no, $awards);
             DB::commit();

+ 3 - 1
app/Services/QianBaoWithdrawService.php

@@ -438,10 +438,11 @@ class QianBaoWithdrawService
     }
 
     //创建提现订单
-    private static function createOrder($memberId, $amount, $channel, $bank_name, $account, $card_no)
+    public static function createOrder($memberId, $amount, $channel, $bank_name, $account, $card_no)
     {
         DB::beginTransaction();
         $result['chat_id'] = $memberId;
+        $result['code'] = 0;
         $default_amount = $amount;
         try {
             $wallet = WalletService::findOne(['member_id' => $memberId]);
@@ -488,6 +489,7 @@ class QianBaoWithdrawService
             DB::rollBack();
             LogService::error($e);
             $result['text'] = "系统发生了错误,请联系管理员";
+            $result['code'] = -1;
             if ($e->getCode() === HttpStatus::CUSTOM_ERROR) {
                 $result['text'] = $e->getMessage();
             }

+ 3 - 1
app/Services/RebateService.php

@@ -92,7 +92,9 @@ class RebateService extends BaseService
     //更新有效投注额
     public static function updateEffectiveBettingAmount($rebate, $amount): void
     {
-        $rebate->increment('effective_betting_amount', $amount);
+        if ($rebate) {
+            $rebate->increment('effective_betting_amount', $amount);
+        }
     }
 
     //笔笔返 根据某天的有效投注额,和反水比例,进行笔笔返

+ 98 - 0
app/Services/SportClientService.php

@@ -0,0 +1,98 @@
+<?php
+
+namespace App\Services;
+
+use Illuminate\Support\Facades\Http;
+
+
+class SportClientService
+{
+
+    //
+    public static function get($endpoint, $params = [])
+    {
+        $response = Http::withHeaders([
+            'x-apisports-key' => env('API_FOOTBALL_KEY'),
+        ])
+            ->withoutVerifying()  // 临时跳过 SSL 验证
+            ->get(env('API_FOOTBALL_HOST') . '/' . $endpoint, $params);
+
+        if ($response->successful()) {
+            return $response->json();
+        }
+
+        // Handle errors as needed
+        throw new \Exception("API request failed: " . $response->body());
+    }
+
+    public static function post($endpoint, $data = [])
+    {
+        $response = Http::withHeaders([
+            'x-apisports-key' => config('services.api_football.key'),
+        ])->post(config('services.api_football.host') . '/' . $endpoint, $data);
+
+        if ($response->successful()) {
+            return $response->json();
+        }
+
+        // Handle errors as needed
+        throw new \Exception("API request failed: " . $response->body());
+    }
+
+    // 时区
+    public static function timezone()
+    {
+        return self::get('timezone');
+    }
+
+    // 国家/地区
+    public static function countries($params = [])
+    {
+        return self::get('countries', $params);
+    }
+
+    //  联赛  获取可用的联赛和杯赛名单。
+    public static function leagues($params = [])
+    {
+        return static::get('leagues', $params);
+    }
+
+    // 联赛赛季  获取特定联赛的赛季列表。
+    public static function leaguesSeasons($params = [])
+    {
+        return static::get('leagues/seasons', $params);
+    }
+
+
+    //  This endpoint returns in-play odds for fixtures in progress.
+    //  此端点会返回正在进行的比赛的实时赔率。
+    //  Update Frequency : This endpoint is updated every 5 seconds.
+    //  更新频率:此端点每 5 秒钟更新一次。
+    public static function oddsLive($params = [])
+    {
+        return static::get('odds/live', $params);
+    }
+
+    public static function odds($params = [])
+    {
+        return static::get('odds', $params);
+    }
+
+    public static function fixturesRounds($params = [])
+    {
+        return static::get('fixtures/rounds', $params);
+    }
+
+
+    // 赛程
+    public static function fixtures($params = [])
+    {
+        return self::get('fixtures', $params);
+    }
+
+    //赛事统计数据
+    public static function statistics($params = [])
+    {
+        return self::get('fixtures/statistics', $params);
+    }
+}

+ 10 - 1
app/Services/UserService.php

@@ -65,6 +65,15 @@ class UserService extends BaseService
         if (isset($search['like_first_name']) && !empty($search['like_first_name'])) {
             $where[] = ['users.first_name', 'like', "%" . $search['like_first_name'] . "%"];
         }
+        if (isset($search['user_code']) && !empty($search['user_code'])) {
+            $where[] = ['users.user_code', '=', $search['user_code']];
+        }
+        if (isset($search['agent_user_code']) && !empty($search['agent_user_code'])) {
+            $where[] = ['users.agent_user_code', '=', $search['agent_user_code']];
+        }
+        if (isset($search['level']) && !empty($search['level'])) {
+            $where[] = ['users.level', '=', $search['level']];
+        }
         return $where;
     }
 
@@ -96,7 +105,7 @@ class UserService extends BaseService
     public static function paginate(array $search = [], $order = 'desc', $by = 'created_at')
     {
         $limit = isset($search['limit']) ? $search['limit'] : 15;
-        $query = static::$MODEL::with(['wallet'])
+        $query = static::$MODEL::with(['wallet','level','agent'])
             ->join('wallets', 'users.member_id', '=', 'wallets.member_id')
             ->where(self::getWhere($search));
         if ($by == 'available_balance') $by = "wallets.{$by}";

+ 4 - 4
config/database.php

@@ -48,13 +48,13 @@ return [
             'url' => env('DATABASE_URL'),
             'host' => env('DB_HOST', '127.0.0.1'),
             'port' => env('DB_PORT', '3306'),
-            'database' => env('DB_DATABASE', 'forge'),
-            'username' => env('DB_USERNAME', 'forge'),
-            'password' => env('DB_PASSWORD', ''),
+            'database' => env('DB_DATABASE', 'bot-28'),
+            'username' => env('DB_USERNAME', 'root'),
+            'password' => env('DB_PASSWORD', '123456'),
             'unix_socket' => env('DB_SOCKET', ''),
             'charset' => 'utf8mb4',
             'collation' => 'utf8mb4_general_ci',
-            'prefix' => env('DB_PREFIX','forge_'),
+            'prefix' => env('DB_PREFIX','bot_'),
             'prefix_indexes' => true,
             'strict' => true,
             'engine' => "InnoDB",

+ 11 - 1
example.env

@@ -5,6 +5,16 @@ APP_NAME=Bot
 APP_DEBUG=true
 APP_URL=
 
+#足球接口域名
+FOOTBALL_APP_URL=https://api.8xbet001.com
+
+# 足球数据API平台的秘钥和地址
+API_FOOTBALL_KEY=b60ecb5e998be566e592068b3f6d98b1
+API_FOOTBALL_HOST=https://v3.football.api-sports.io
+
+# 是否发送电报
+SEND_TELEGRAM = true
+
 # 短信宝
 SMS_USERNAME=
 SMS_PASSWORD=
@@ -68,7 +78,7 @@ LOG_LEVEL=error
 BROADCAST_DRIVER=log
 CACHE_DRIVER=file
 FILESYSTEM_DISK=local
-QUEUE_CONNECTION=sync
+QUEUE_CONNECTION=database
 SESSION_DRIVER=file
 SESSION_LIFETIME=120
 MEMCACHED_HOST=127.0.0.1

File diff suppressed because it is too large
+ 0 - 0
fixturesStatistics-2.json


+ 1 - 0
fixturesStatistics.json

@@ -0,0 +1 @@
+{"get":"fixtures\/statistics","parameters":{"fixture":"1386532"},"errors":[],"results":2,"paging":{"current":1,"total":1},"response":[{"team":{"id":1844,"name":"Salford City","logo":"https:\/\/media.api-sports.io\/football\/teams\/1844.png"},"statistics":[{"type":"Shots on Goal","value":4},{"type":"Shots off Goal","value":4},{"type":"Total Shots","value":9},{"type":"Blocked Shots","value":1},{"type":"Shots insidebox","value":4},{"type":"Shots outsidebox","value":5},{"type":"Fouls","value":5},{"type":"Corner Kicks","value":6},{"type":"Offsides","value":0},{"type":"Ball Possession","value":"49%"},{"type":"Yellow Cards","value":2},{"type":"Red Cards","value":null},{"type":"Goalkeeper Saves","value":3},{"type":"Total passes","value":318},{"type":"Passes accurate","value":200},{"type":"Passes %","value":"63%"},{"type":"expected_goals","value":null},{"type":"goals_prevented","value":null}]},{"team":{"id":1832,"name":"Bromley","logo":"https:\/\/media.api-sports.io\/football\/teams\/1832.png"},"statistics":[{"type":"Shots on Goal","value":3},{"type":"Shots off Goal","value":6},{"type":"Total Shots","value":12},{"type":"Blocked Shots","value":3},{"type":"Shots insidebox","value":7},{"type":"Shots outsidebox","value":5},{"type":"Fouls","value":12},{"type":"Corner Kicks","value":4},{"type":"Offsides","value":3},{"type":"Ball Possession","value":"51%"},{"type":"Yellow Cards","value":1},{"type":"Red Cards","value":null},{"type":"Goalkeeper Saves","value":2},{"type":"Total passes","value":321},{"type":"Passes accurate","value":197},{"type":"Passes %","value":"61%"},{"type":"expected_goals","value":null},{"type":"goals_prevented","value":null}]}]}

+ 88 - 3
lang/en/messages.php

@@ -319,7 +319,92 @@ return [
     "账号异常" => "Account anomaly",
     "提现不能少于20 USDT,请重试"=>"⚠️ Withdrawal cannot be less than 20 RMB, please try again.",
     "您有未完成的活动" => "You have unfinished activities.",
-
-
-
+    //赔率玩法
+    'Home' => 'Home Win',
+    'Draw' => 'Draw',
+    'Away' => 'Away Win',
+    'Yes' => 'Yes',
+    'No' => 'No',
+    'Home or Draw' => 'Home Win or Draw',
+    'Away or Draw' => 'Away Win or Draw',
+    'Home or Away' => 'Home Win or Away Win',
+    'Draw/Over ' => 'Draw/Over %s Goals',
+    'Away/Over ' => 'Away Win/Over %s Goals',
+    'Home/Over ' => 'Home Win/Over %s Goals',
+    'Draw/Under ' => 'Draw/Under %s Goals',
+    'Away/Under ' => 'Away Win/Under %s Goals',
+    'Home/Under ' => 'Home Win/Under %s Goals',
+    'Over ' => 'Over %s Goals',
+    'Under ' => 'Under %s Goals',
+    'o/yes ' => 'Over %s Goals/Yes',
+    'o/no ' => 'Under %s Goals/No',
+    'u/yes ' => 'Under %s Goals/Yes',
+    'u/no ' => 'Over %s Goals/No',
+    'No goal' => 'No Goal',
+    ' or more' => '%s Goals or More',
+    'more ' => 'Over %s Goals',
+    'Over' => 'Over',
+    'Under' => 'Under',
+    'Home/Yes' => 'Home Win/Yes',
+    'Home/No' => 'Home Win/No',
+    'Away/Yes' => 'Away Win/Yes',
+    'Away/No' => 'Away Win/No',
+    'Draw/Yes' => 'Draw/Yes',
+    'Draw/No' => 'Draw/No',
+    '1st Half' => 'First Half',
+    '2nd Half' => 'Second Half',
+    'Home/Draw' => 'Home Win/Draw',
+    'Home/Away' => 'Home Win/Away Win',
+    'Home/Home' => 'Home Win/Home Win',
+    'Away/Home' => 'Away Win/Home Win',
+    'Away/Away' => 'Away Win/Away Win',
+    'Away/Draw' => 'Away Win/Draw',
+    'Draw/Home' => 'Draw/Home Win',
+    'Draw/Draw' => 'Draw/Draw',
+    'Draw/Away' => 'Draw/Away Win',
+    "操" => " Operation ",
+    "尾" => " Tail ",
+    "头" => " First Digit ",
+    '大' => 'Big',
+    '小' => 'Small',
+    '单' => 'Odd',
+    '双' => 'Even',
+    '大单' => 'Big Odd',
+    '大双' => 'Big Even',
+    '小单' => 'Small Odd',
+    '小双' => 'Small Even',
+    '极大' => 'Huge',
+    '极小' => 'Tiny',
+    '对子' => 'Pair',
+    '顺子' => 'Straight',
+    '豹子' => 'Triple',
+    '尾大' => 'Tail Big',
+    '尾小' => 'Tail Small',
+    '尾单' => 'Tail Odd',
+    '尾双' => 'Tail Even',
+    '尾大单' => 'Tail Big Odd',
+    '尾小单' => 'Tail Small Odd',
+    '尾大双' => 'Tail Big Even',
+    '尾小双' => 'Tail Small Even',
+    'A大' => 'A Big',
+    'A小' => 'A Small',
+    'A单' => 'A Odd',
+    'A双' => 'A Even',
+    'B大' => 'B Big',
+    'B小' => 'B Small',
+    'B单' => 'B Odd',
+    'B双' => 'B Even',
+    'C大' => 'C Big',
+    'C小' => 'C Small',
+    'C单' => 'C Odd',
+    'C双' => 'C Even',
+    '删除成功' => 'Delete Success',
+    '找不到此记录' => 'Not Found This Record',
+    '提交成功' => 'Submit Success',
+    '已达添加上限' => 'Reach Add Limit',
+    '资金密码错误' => 'Password Error',
+    '请先设置资金密码' => 'Please Set Password First',
+    '提现不能少于' => 'Withdrawal Cannot Be Less Than ',
+    '余额不足' => 'Insufficient Balance',
+    '充值类型错误' => 'Recharge Type Error',
 ];

+ 88 - 2
lang/vi/messages.php

@@ -319,6 +319,92 @@ return [
     "账号异常" => "Tài khoản bất thường",
     "提现不能少于20 USDT,请重试"=>"⚠️Số tiền rút tối thiểu là 20 USDT, vui lòng thử lại",
     "您有未完成的活动" => "Bạn có các hoạt động chưa hoàn thành",
-
-
+    //Tỷ lệ cược
+    'Home' => 'Thắng nhà',
+    'Draw' => 'Hòa',
+    'Away' => 'Thắng khách',
+    'Yes' => 'Có',
+    'No' => 'Không',
+    'Home or Draw' => 'Thắng nhà hoặc hòa',
+    'Away or Draw' => 'Thắng khách hoặc hòa',
+    'Home or Away' => 'Thắng nhà hoặc thắng khách',
+    'Draw/Over ' => 'Hòa/Hơn %s bàn',
+    'Away/Over ' => 'Thắng khách/Hơn %s bàn',
+    'Home/Over ' => 'Thắng nhà/Hơn %s bàn',
+    'Draw/Under ' => 'Hòa/Không quá %s bàn',
+    'Away/Under ' => 'Thắng khách/Không quá %s bàn',
+    'Home/Under ' => 'Thắng nhà/Không quá %s bàn',
+    'Over ' => 'Hơn %s bàn',
+    'Under ' => 'Không quá %s bàn',
+    'o/yes ' => 'Hơn %s bàn/Có',
+    'o/no ' => 'Không quá %s bàn/Không',
+    'u/yes ' => 'Không quá %s bàn/Có',
+    'u/no ' => 'Hơn %s bàn/Không',
+    'No goal' => 'Không có bàn thắng',
+    ' or more' => '%s bàn trở lên',
+    'more ' => 'Hơn %s bàn',
+    'Over' => 'Hơn',
+    'Under' => 'Không quá',
+    'Home/Yes' => 'Thắng nhà/Có',
+    'Home/No' => 'Thắng nhà/Không',
+    'Away/Yes' => 'Thắng khách/Có',
+    'Away/No' => 'Thắng khách/Không',
+    'Draw/Yes' => 'Hòa/Có',
+    'Draw/No' => 'Hòa/Không',
+    '1st Half' => 'Hiệp 1',
+    '2nd Half' => 'Hiệp 2',
+    'Home/Draw' => 'Thắng nhà/Hòa',
+    'Home/Away' => 'Thắng nhà/Thắng khách',
+    'Home/Home' => 'Thắng nhà/Thắng nhà',
+    'Away/Home' => 'Thắng khách/Thắng nhà',
+    'Away/Away' => 'Thắng khách/Thắng khách',
+    'Away/Draw' => 'Thắng khách/Hòa',
+    'Draw/Home' => 'Hòa/Thắng nhà',
+    'Draw/Draw' => 'Hòa/Hòa',
+    'Draw/Away' => 'Hòa/Thắng khách',
+    "操" => " Thao tác ",
+    "尾" => " Đuôi ",
+    "头" => " Đầu số ",
+    '大' => 'Tài (Lớn)',
+    '小' => 'Xỉu (Nhỏ)',
+    '单' => 'Lẻ',
+    '双' => 'Chẵn',
+    '大单' => 'Tài Lẻ',
+    '大双' => 'Tài Chẵn',
+    '小单' => 'Xỉu Lẻ',
+    '小双' => 'Xỉu Chẵn',
+    '极大' => 'Cực lớn',
+    '极小' => 'Cực nhỏ',
+    '对子' => 'Đôi',
+    '顺子' => 'Sảnh',
+    '豹子' => 'Bão (Ba con giống nhau)',
+    '尾大' => 'Đuôi Lớn',
+    '尾小' => 'Đuôi Nhỏ',
+    '尾单' => 'Đuôi Lẻ',
+    '尾双' => 'Đuôi Chẵn',
+    '尾大单' => 'Đuôi Lớn Lẻ',
+    '尾小单' => 'Đuôi Nhỏ Lẻ',
+    '尾大双' => 'Đuôi Lớn Chẵn',
+    '尾小双' => 'Đuôi Nhỏ Chẵn',
+    'A大' => 'A Lớn',
+    'A小' => 'A Nhỏ',
+    'A单' => 'A Lẻ',
+    'A双' => 'A Chẵn',
+    'B大' => 'B Lớn',
+    'B小' => 'B Nhỏ',
+    'B单' => 'B Lẻ',
+    'B双' => 'B Chẵn',
+    'C大' => 'C Lớn',
+    'C小' => 'C Nhỏ',
+    'C单' => 'C Lẻ',
+    'C双' => 'C Chẵn',
+    '删除成功' => 'Xóa thành công',
+    '找不到此记录' => 'Không tìm thấy bản ghi',
+    '提交成功' => 'Gửi thành công',
+    '已达添加上限' => 'Đã đạt giới hạn thêm',
+    '资金密码错误' => 'Mật khẩu tài khoản không đúng',
+    '请先设置资金密码' => 'Vui lòng thiết lập mật khẩu tài khoản trước',
+    '提现不能少于' => 'Số tiền rút tối thiểu là ',
+    '余额不足' => 'Số tiền không đủ',
+    '充值类型错误' => 'Loại nạp không đúng',
 ];

+ 88 - 2
lang/zh/messages.php

@@ -319,6 +319,92 @@ return [
     "账号异常" => "账号异常",
     "提现不能少于20 USDT,请重试" => "⚠️提现不能少于20 USDT,请重试",
     "您有未完成的活动" => "您有未完成的活动",
-
-
+    //赔率玩法
+    'Home' => '主胜',
+    'Draw' => '平局',
+    'Away' => '客胜',
+    'Yes' => '是',
+    'No' => '否',
+    'Home or Draw' => '主胜或平局',
+    'Away or Draw' => '客胜或平局',
+    'Home or Away' => '主胜或客胜',
+    'Draw/Over ' => '平局/大于%s球',
+    'Away/Over ' => '客胜/大于%s球',
+    'Home/Over ' => '主胜/大于%s球',
+    'Draw/Under ' => '平局/小于%s球',
+    'Away/Under ' => '客胜/小于%s球',
+    'Home/Under ' => '主胜/小于%s球',
+    'Over ' => '大于%s球',
+    'Under ' => '小于%s球',
+    'o/yes ' => '大于%s球/是',
+    'o/no ' => '小于%s球/否',
+    'u/yes ' => '小于%s球/是',
+    'u/no ' => '大于%s球/否',
+    'No goal' => '无进球',
+    ' or more' => '%s球以上',
+    'more ' => '大于%s球',
+    'Over' => '大于',
+    'Under' => '小于',
+    'Home/Yes' => '主胜/是',
+    'Home/No' => '主胜/否',
+    'Away/Yes' => '客胜/是',
+    'Away/No' => '客胜/否',
+    'Draw/Yes' => '平局/是',
+    'Draw/No' => '平局/否',
+    '1st Half' => '上半场',
+    '2nd Half' => '下半场',
+    'Home/Draw' => '主胜/平局',
+    'Home/Away' => '主胜/客胜',
+    'Home/Home' => '主胜/主胜',
+    'Away/Home' => '客胜/主胜',
+    'Away/Away' => '客胜/客胜',
+    'Away/Draw' => '客胜/平局',
+    'Draw/Home' => '平局/主胜',
+    'Draw/Draw' => '平局/平局',
+    'Draw/Away' => '平局/客胜',
+    "操" => "操",
+    "尾" => "尾",
+    "头" => "头",
+    "大" => "大",
+    "小" => "小",
+    "单" => "单",
+    "双" => "双",
+    '大单' => '大单',
+    '大双' => '大双',
+    '小单' => '小单',
+    '小双' => '小双',
+    '极大' => '极大',
+    '极小' => '极小',
+    '对子' => '对子',
+    '顺子' => '顺子',
+    '豹子' => '豹子',
+    '尾大' => '尾大',
+    '尾小' => '尾小',
+    '尾单' => '尾单',
+    '尾双' => '尾双',
+    '尾大单' => '尾大单',
+    '尾小单' => '尾小单',
+    '尾大双' => '尾大双',
+    '尾小双' => '尾小双',
+    'A大' => 'A大',
+    'A小' => 'A小',
+    'A单' => 'A单',
+    'A双' => 'A双',
+    'B大' => 'B大',
+    'B小' => 'B小',
+    'B单' => 'B单',
+    'B双' => 'B双',
+    'C大' => 'C大',
+    'C小' => 'C小',
+    'C单' => 'C单',
+    'C双' => 'C双',
+    '删除成功' => '删除成功',
+    '找不到此记录' => '找不到此记录',
+    '提交成功' => '提交成功',
+    '已达添加上限' => '已达添加上限',
+    '资金密码错误' => '资金密码错误',
+    '请先设置资金密码' => '请先设置资金密码',
+    '提现不能少于' => '提现不能少于',
+    '余额不足' => '余额不足',
+    '充值类型错误' => '充值类型错误',
 ];

+ 59 - 0
routes/admin.php

@@ -27,6 +27,15 @@ use App\Http\Controllers\admin\Bet;
 use App\Http\Controllers\admin\Rebate;
 use App\Http\Controllers\admin\PaymentOrder;
 use App\Http\Controllers\admin\PcIssue;
+use App\Http\Controllers\admin\Order;
+use App\Http\Controllers\admin\Operation;
+use App\Http\Controllers\admin\Banner;
+use App\Http\Controllers\admin\LhcOrder;
+use App\Http\Controllers\admin\LhcNumber;
+use App\Http\Controllers\admin\LhcLottery;
+use App\Http\Controllers\admin\Sport;
+use App\Http\Controllers\admin\Level;
+use App\Http\Controllers\admin\YueBao;
 
 Route::post('/login', [Admin::class, 'login']);
 Route::get('/test', [Wallet::class, 'test']);
@@ -181,6 +190,8 @@ Route::middleware(['admin.jwt'])->group(function () {
             Route::get('/getPendingTasks', [Wallet::class, 'getPendingTasks']);
             Route::get('/getChangeTypes', [Wallet::class, 'getChangeTypes']);
             Route::post('/debiting', [Wallet::class, 'debiting']);
+            Route::post('/orderDebiting', [Wallet::class, 'orderDebiting']);
+            Route::post('/orderTopUp', [Wallet::class, 'orderTopUp']);
 
 
         });
@@ -191,6 +202,7 @@ Route::middleware(['admin.jwt'])->group(function () {
             Route::post('/merge', [User::class, 'merge']);
             Route::post('/setNote', [User::class, 'setNote']);
             Route::post('/banned', [User::class, 'banned']);
+            Route::get('/loginLog', [User::class, 'loginLog']);
 
 
         });
@@ -211,6 +223,53 @@ Route::middleware(['admin.jwt'])->group(function () {
 
         });
 
+        Route::prefix('/order')->group(function () {
+            Route::post('/refund', [Order::class, 'refund']);
+            Route::get('/list', [Order::class, 'list']);
+            Route::get('/info', [Order::class, 'info']);
+        });
+        Route::prefix('/operation')->group(function () {
+            Route::get('/exchangeList', [Operation::class, 'exchangeList']);
+            Route::get('/index', [Operation::class, 'index']);
+        });
+        Route::prefix('/banner')->group(function () {
+            Route::get('/index', [Banner::class, 'index']);
+            Route::post('/update', [Banner::class, 'update']);
+            Route::get('/delete', [Banner::class, 'delete']);
+        });
+        
+        Route::prefix('/lhcOrder')->group(function () {
+            Route::get('/list', [LhcOrder::class, 'list']);
+            Route::get('/info', [LhcOrder::class, 'info']);
+            Route::post('/refund', [LhcOrder::class, 'refund']);
+        });
+        Route::prefix('/lhcNumber')->group(function () {
+            Route::get('/list', [LhcNumber::class, 'list']);
+        });
+        Route::prefix('/lhcLottery')->group(function () {
+            Route::get('/list', [LhcLottery::class, 'list']);
+        });
+
+        
+        Route::prefix('/sport')->group(function () {
+            Route::get('/list', [Sport::class, 'list']);
+            Route::get('/info', [Sport::class, 'info']);
+            Route::post('/setStatus', [Sport::class, 'setStatus']);
+            Route::post('/setOddsLocked', [Sport::class, 'setOddsLocked']);
+            Route::post('/getFixtures', [Sport::class, 'getFixtures']);
+            Route::post('/setValuesLocked', [Sport::class, 'setValuesLocked']);
+        });
+
+        Route::prefix('/level')->group(function () {
+            Route::get('/list', [Level::class, 'list']);
+            Route::post('/update', [Level::class, 'update']);
+            Route::get('/delete', [Level::class, 'delete']);
+        });
+
+        Route::prefix('/yuebao')->group(function () {
+            Route::get('/item', [YueBao::class, 'item']);
+            Route::get('/log', [YueBao::class, 'log']);
+        });
     });
 
 

+ 32 - 0
routes/api.php

@@ -9,6 +9,8 @@ use App\Http\Controllers\api\Home;
 use App\Http\Controllers\api\Issue;
 use App\Http\Controllers\api\Pay;
 use App\Http\Controllers\api\NewPc;
+use App\Http\Controllers\api\PcIssue;
+use App\Http\Controllers\api\Wallet;
 
 Route::post("/onMessage", [TelegramWebHook::class, 'handle']);
 Route::get("/setWebHook", [Home::class, 'setWebHook']);
@@ -49,6 +51,16 @@ Route::prefix('/issue')->group(function () {
 
 });
 
+//足球app请求的接口,根据游戏模式返回不同的数据
+Route::prefix('/pcissue')->group(function () {
+    Route::get("/", [PcIssue::class, 'index']);
+    Route::get("/cao", [PcIssue::class, 'cao']);
+    Route::get("/countdown", [PcIssue::class, 'countdown']);
+    Route::get("/prediction", [PcIssue::class, 'prediction']);
+    Route::get("/history", [PcIssue::class, 'history']);
+    Route::get("/yuanTou", [PcIssue::class, 'yuanTou']);
+});
+
 
 Route::get('/test', [Home::class, 'test']);
 Route::prefix('/pay')->group(function () {
@@ -66,6 +78,26 @@ Route::fallback(function () {
     ], 200);
 });
 
+// 足球app充值提现的接口需要校验 token 的接口
+Route::middleware('check.token')->group(function () {
+    Route::prefix('/wallet')->group(function () {
+        Route::get("/scan", [Wallet::class, 'scan']);
+        Route::post("/recharge", [Wallet::class, 'recharge']);
+        Route::get("/channel", [Wallet::class, 'getChannel']);
+        Route::post("/createPay", [Wallet::class, 'createPay']);
+        Route::get("/withdrawChannel", [Wallet::class, 'withdrawChannel']);
+        Route::post("/autoPayout", [Wallet::class, 'autoPayout']);
+        Route::post("/payout", [Wallet::class, 'payout']);
+        Route::post("/withdraw", [Wallet::class, 'withdraw']);
+        Route::post("/addBank", [Wallet::class, 'addBank']);
+        Route::get("/delBank", [Wallet::class, 'delBank']);
+        Route::get("/bankList", [Wallet::class, 'bankList']);
+
+        Route::post("/addAddress", [Wallet::class, 'addAddress']);
+        Route::get("/delAddress", [Wallet::class, 'delAddress']);
+        Route::get("/address", [Wallet::class, 'address']);
+    });
+});
 
 
 

Some files were not shown because too many files changed in this diff