Tree 3 місяців тому
батько
коміт
eb493a0906
100 змінених файлів з 10950 додано та 2 видалено
  1. 0 0
      LICENSE
  2. 0 2
      README.md
  3. 42 0
      app/AdminConfig.php
  4. 47 0
      app/AdminMenu.php
  5. 29 0
      app/AdminPermission.php
  6. 27 0
      app/AdminRole.php
  7. 119 0
      app/AdminUser.php
  8. 90 0
      app/Console/Commands/DbQueue.php
  9. 64 0
      app/Console/Commands/DecStock.php
  10. 241 0
      app/Console/Commands/ExemptReview.php
  11. 49 0
      app/Console/Commands/Extension.php
  12. 91 0
      app/Console/Commands/OnLineMax.php
  13. 86 0
      app/Console/Commands/OnlineReport.php
  14. 44 0
      app/Console/Commands/Opcache.php
  15. 109 0
      app/Console/Commands/PayOrder.php
  16. 293 0
      app/Console/Commands/RecordPlatformData.php
  17. 65 0
      app/Console/Commands/RecordServerGameCount.php
  18. 59 0
      app/Console/Commands/RecordServerGameCountYesterday.php
  19. 85 0
      app/Console/Commands/RecordThreeGameYesterday.php
  20. 112 0
      app/Console/Commands/RecordUserScoreChangeStatistics.php
  21. 75 0
      app/Console/Commands/Redis/FissionCommand.php
  22. 45 0
      app/Console/Commands/Redis/PublishCommand.php
  23. 95 0
      app/Console/Commands/Redis/SubCommand.php
  24. 66 0
      app/Console/Commands/StockChange.php
  25. 91 0
      app/Console/Commands/TestCon.php
  26. 71 0
      app/Console/Kernel.php
  27. 88 0
      app/Exceptions/Handler.php
  28. 19 0
      app/Facade/RedisConnect.php
  29. 35 0
      app/Facade/TableName.php
  30. 22 0
      app/Game/AgentBonusRecord.php
  31. 76 0
      app/Game/AgentCommission.php
  32. 103 0
      app/Game/AgentLinks.php
  33. 19 0
      app/Game/AgentLinksClickLog.php
  34. 109 0
      app/Game/AgentUser.php
  35. 43 0
      app/Game/AgentUserRecord.php
  36. 75 0
      app/Game/Banner.php
  37. 40 0
      app/Game/BetBy/BetItem.php
  38. 37 0
      app/Game/BetBy/BetSlipItem.php
  39. 51 0
      app/Game/BetBy/BonusItem.php
  40. 19 0
      app/Game/BetBy/BonusTemplate.php
  41. 19 0
      app/Game/BetBy/ComboboostDataItem.php
  42. 16 0
      app/Game/BetBy/ErrorItem.php
  43. 21 0
      app/Game/BetBy/FreebetDataItem.php
  44. 18 0
      app/Game/BetBy/PlayerDataItem.php
  45. 21 0
      app/Game/BetBy/PlayerDetails.php
  46. 16 0
      app/Game/BetBy/PlayerSegment.php
  47. 19 0
      app/Game/BetBy/RestrictionItem.php
  48. 52 0
      app/Game/BetBy/TemplateItem.php
  49. 39 0
      app/Game/BetBy/TransactionItem.php
  50. 107 0
      app/Game/BigWinner.php
  51. 16 0
      app/Game/Block.php
  52. 11 0
      app/Game/Config/GameBasicConfig.php
  53. 117 0
      app/Game/GameCard.php
  54. 194 0
      app/Game/GlobalUserInfo.php
  55. 88 0
      app/Game/LogGamecardClick.php
  56. 330 0
      app/Game/Logics/SendCodeLogic.php
  57. 8 0
      app/Game/PG/SpinResult.php
  58. 244 0
      app/Game/PageModule.php
  59. 16 0
      app/Game/QuickAccountPass.php
  60. 16 0
      app/Game/QuickAccountPassStore.php
  61. 55 0
      app/Game/RePayConfig.php
  62. 15 0
      app/Game/RedEnvelopeConfig.php
  63. 15 0
      app/Game/RedEnvelopeLog.php
  64. 47 0
      app/Game/RouteModel.php
  65. 229 0
      app/Game/Services/AgentService.php
  66. 110 0
      app/Game/Services/AgentSystemService.php
  67. 442 0
      app/Game/Services/AtmosferaService.php
  68. 241 0
      app/Game/Services/AviatrixService.php
  69. 439 0
      app/Game/Services/BetbyService.php
  70. 434 0
      app/Game/Services/BetbyTestService.php
  71. 30 0
      app/Game/Services/CheckGameLogin.php
  72. 594 0
      app/Game/Services/EvoplayService.php
  73. 107 0
      app/Game/Services/GA4MeasurementProtocolClient.php
  74. 105 0
      app/Game/Services/GameEncrypt.php
  75. 91 0
      app/Game/Services/LZCompressor/LZContext.php
  76. 37 0
      app/Game/Services/LZCompressor/LZData.php
  77. 33 0
      app/Game/Services/LZCompressor/LZReverseDictionary.php
  78. 363 0
      app/Game/Services/LZCompressor/LZString.php
  79. 110 0
      app/Game/Services/LZCompressor/LZUtil.php
  80. 94 0
      app/Game/Services/LZCompressor/LZUtil16.php
  81. 466 0
      app/Game/Services/LuckyStreakService.php
  82. 31 0
      app/Game/Services/MustGameLogin.php
  83. 116 0
      app/Game/Services/OuroGameService.php
  84. 124 0
      app/Game/Services/PPlayService.php
  85. 124 0
      app/Game/Services/PPlayTestService.php
  86. 98 0
      app/Game/Services/PgSoftService.php
  87. 91 0
      app/Game/Services/PgSoftTestService.php
  88. 191 0
      app/Game/Services/PlatformService.php
  89. 156 0
      app/Game/Services/RouteService.php
  90. 162 0
      app/Game/Services/ServerService.php
  91. 42 0
      app/Game/Services/TelegramAppService.php
  92. 80 0
      app/Game/Services/TonGiftsService.php
  93. 16 0
      app/Game/Style.php
  94. 30 0
      app/Game/Telegram/TelegramUser.php
  95. 72 0
      app/Game/WebActivity.php
  96. 136 0
      app/Game/WebChannelConfig.php
  97. 223 0
      app/Http/AppFlyerEvent/AppflyerEvent.php
  98. 363 0
      app/Http/Controllers/Admin/AccCallbackController.php
  99. 63 0
      app/Http/Controllers/Admin/AdminLogController.php
  100. 576 0
      app/Http/Controllers/Admin/AdministratorController.php

+ 0 - 2
README.md

@@ -1,2 +0,0 @@
-# admin_api
-

+ 42 - 0
app/AdminConfig.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App;
+
+use Illuminate\Database\Eloquent\Model;
+
+class AdminConfig extends Model
+{
+    protected $fillable = ['name', 'config_key', 'config_value', 'type'];
+    protected $connection = 'write';
+    public static function getValue($key)
+    {
+        $instance = new static;
+        if (is_array($key)) {
+            $result = $instance->whereIn('config_key', $key)->get();
+            if ($result) {
+                $data = $result->flatMap(function ($config) {
+                    return [$config->config_key => $config->config_value];
+                })->toArray();
+            } else {
+                $data = [];
+            }
+            return $data;
+        } else {
+            $result = $instance->where('config_key', $key)->first();
+            if (!$result) {
+                return null;
+            }
+            return $result->config_value;
+        }
+    }
+
+    public function scopeSearchCondition($query, string $keyword = null)
+    {
+        if (is_null($keyword)) {
+            return $query;
+        } else {
+            return $query->where("config_key", "like", "%{$keyword}%")
+                ->orWhere("name", "like", "%{$keyword}%");
+        }
+    }
+}

+ 47 - 0
app/AdminMenu.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace App;
+
+use App\Scopes\SortScope;
+use Illuminate\Database\Eloquent\Model;
+
+class AdminMenu extends Model
+{
+    protected $fillable = ['name', 'url', 'icon', 'sort', 'pid'];
+    protected $connection = 'write';
+    protected static function boot()
+    {
+        parent::boot();
+
+        static::addGlobalScope(new SortScope);
+    }
+    public function getNameAttribute($value)
+    {
+        $langv=trans("menu.".$value);
+        if(strstr($langv,"menu."))return $value;
+        return $langv??$value;
+    }
+    //
+    public function roles()
+    {
+        return $this->belongsToMany(AdminRole::class);
+    }
+
+    /**
+     * 无限分级菜单,获取下一级菜单
+     *
+     */
+    public function children()
+    {
+        return $this->hasMany(self::class, 'pid');
+    }
+
+    /**
+     * 无限分级菜单,获取上一级菜单
+     *
+     */
+    public function parent()
+    {
+        return $this->belongsTo(self::class, 'pid');
+    }
+}

+ 29 - 0
app/AdminPermission.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Collection;
+
+class AdminPermission extends Model
+{
+    protected $fillable = ['name', 'routes'];
+    protected $connection = 'write';
+    public function roles()
+    {
+        return $this->belongsToMany(AdminRole::class);
+    }
+
+    protected function setRoutesAttribute($routes)
+    {
+        if (!($routes instanceof Collection)) {
+            $routes = new Collection($routes);
+        }
+        $this->attributes['routes'] = $routes->implode(',');
+    }
+
+    protected function getRoutesAttribute($routeStr)
+    {
+        return new Collection(explode(',', $routeStr));
+    }
+}

+ 27 - 0
app/AdminRole.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App;
+
+use App\AdminUser;
+use Illuminate\Database\Eloquent\Model;
+
+class AdminRole extends Model
+{
+    protected $connection = 'write';
+    protected $fillable = ['name', 'description'];
+
+    public function users()
+    {
+        return $this->belongsToMany(AdminUser::class);
+    }
+
+    public function menus()
+    {
+        return $this->belongsToMany(AdminMenu::class);
+    }
+
+    public function permissions()
+    {
+        return $this->belongsToMany(AdminPermission::class);
+    }
+}

+ 119 - 0
app/AdminUser.php

@@ -0,0 +1,119 @@
+<?php
+
+namespace App;
+
+use App\AdminRole;
+use App\Utility\Rbac;
+use Illuminate\Container\Container;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Hash;
+
+class AdminUser extends Model
+{
+    protected $connection = 'write';
+    protected $fillable = ['avatar', 'nickname', 'account', 'password', 'lottery_amount', 'recharge_amount', 'type','channel','locale'];
+    protected $hidden = ['password'];
+
+    public function roles()
+    {
+        return $this->belongsToMany(AdminRole::class);
+    }
+
+    public function getMenus()
+    {
+        $roles = $this->roles;
+        $dealTopMenuFunc = function ($topMenu) {
+            $topMenu->hasChild = $topMenu->children->isNotEmpty();
+            return $topMenu;
+        };
+        if ($this->hasSuperRole()) {
+            $menus = AdminMenu::where('pid', 0)
+                ->get()
+                ->map($dealTopMenuFunc);
+
+        } else {
+            $menus = $roles
+                ->map(function ($role) {
+                    return $role->menus()->where('pid', 0)->get();
+                })
+                ->collapse()
+                ->map($dealTopMenuFunc);
+        }
+        return $menus;
+    }
+
+    function get_tree($array, $parent_id = 0)
+    {
+        $tree = [];
+        foreach ($array as &$val) {
+            if ($val['pid'] == $parent_id) {
+
+                $val['child'] = $this->get_tree($array, $val['id']);
+
+                $tree[] = $val;
+            }
+        }
+        return $tree;
+    }
+
+    public function getPermissionRoutes()
+    {
+        if ($this->hasSuperRole()) {
+            $permissions = Rbac::getAllRoutes()
+                ->map(function ($route) {
+                    return $route->rbacRule;
+                });
+        } else {
+            $permissions = $this->roles->map(function ($role) {
+                return $role->permissions;
+            })
+                ->collapse()
+                ->map(function ($permission) {
+                    return $permission->routes;
+                })
+                ->collapse();
+        }
+        return $permissions;
+    }
+
+    public function hasSuperRole()
+    {
+        $hasSuperRole = false;
+        $this->roles->each(function ($role) use (&$hasSuperRole) {
+            if ($role->id === 1 || $role->id === 12) {
+                $hasSuperRole = true;
+                return false;
+            }
+        });
+        return $hasSuperRole;
+    }
+
+    public static function isExist(string $account, string $type)
+    {
+        $instance = new static;
+        return $instance->where('account', $account)->where('type', $type)->count() > 0;
+    }
+
+    public function isExistForUpdate(string $account, string $type)
+    {
+        return $this->where('id', '!=', $this->id)
+                ->where('account', $account)
+                ->where('type', $type)
+                ->count() > 0;
+    }
+
+    public function getAvatarAttribute($avatar): string
+    {
+        return $avatar ?? '/uploads/avatar/20181031/5bd90252493d1.jpg';
+    }
+
+    protected function setPasswordAttribute($value)
+    {
+        if (is_null($value) || $value === '') {
+            return;
+        }
+        $this->attributes['password'] = Hash::make($value);
+    }
+}

+ 90 - 0
app/Console/Commands/DbQueue.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\helper\Helper;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+
+class DbQueue extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'db_queue';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '批量处理redis队列插入数据';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        /*
+         * redis队列数据格式要求:
+         * db: 数据库前缀;table: 表名;type: 操作类型;data: 数据表内字段和数据,字段需要与表字段保持一致
+         * 例: {"db": "QPRecordDB.dbo.", "table": "RecordNewThunderBonueLogs", "type": "insert", "data": {"UserID": 123, "KindID": 6001, "SortID": 1,"BuyBase": 600, "BuyScore": 60000, "ChangeScore": 0, "Control": 10, "Type": 2}}
+         */
+        $redis = Redis::connection('ServerGameRedis');
+
+        while(true){
+            $queueData = $redis->command('brpop', ['dbQueue', 30]);
+            if ($queueData && count($queueData) > 1) {
+                $jsonArr = json_decode($queueData[1], true);
+                $data = [];
+                if ($jsonArr) {
+                    foreach ($jsonArr['data'] as $k => $v) {
+                        $data[$k] = $v;
+                    }
+                    Log::info('Redis队列任务:' . json_encode($data));
+                    if($jsonArr['table'] == 'UserWinRank'&&false){
+                        DB::connection('sqlsrv')->table($jsonArr['db'] . $jsonArr['table'])->where('UserID',$data['UserID'])->increment('Score',$data['Score']);
+                        if(@$data['CellScore']){
+                            //更新子游戏榜单
+                            $obj = [
+                                'CellScore' => $data['CellScore'],
+                                'Datatime' => date('Y-m-d H:i:s'),
+                                'Icon' => "1,11,3",
+                                "Multiple" => intval($data['Score']/$data['CellScore']),
+                                "NickName" => $data['NickName'],
+                                "TotalScore" => $data['Score'],
+                                "UserID" => $data['UserID']
+                            ];
+                            $redis->zadd('subgameranking_'.$data['KindID'].'_'.$data['SortID'],intval($data['Score']/$data['CellScore']),json_encode($obj));
+                        }
+
+                    }else if($jsonArr['table'] == 'RecordNewThunderBonueLogs'){
+                        DB::connection('sqlsrv')->table($jsonArr['db'] . $jsonArr['table'])->insert($data);
+                    }else if($jsonArr['table'] == 'RecordJackpot'){
+                        $data['created'] = date('Y-m-d H:i:s');
+                        DB::connection('sqlsrv')->table($jsonArr['db'] . $jsonArr['table'])->insert($data);
+                    }
+
+                }
+            }else{
+                sleep(5);
+            }
+        }
+    }
+}

+ 64 - 0
app/Console/Commands/DecStock.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Facade\TableName;
+use App\Http\helper\NumConfig;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+
+class DecStock extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'dec_stock';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '减少crash库存';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+
+        $limitValue = 18000 * NumConfig::NUM_VALUE;
+        $decValue = 3000 * NumConfig::NUM_VALUE;
+
+        $list = DB::table(TableName::QPPlatformDB() . 'RoomStockStatic')
+            ->where('GameID', 4020)
+            ->where('Stock', '>=', $limitValue)
+            ->get();
+
+        if ($list->isNotEmpty()) {
+            foreach ($list as $value) {
+                DB::table(TableName::QPPlatformDB() . 'RoomStockStatic')
+                    ->where('ID', $value->ID)
+                    ->decrement('Stock', $decValue);
+            }
+        }
+
+    }
+
+
+}

+ 241 - 0
app/Console/Commands/ExemptReview.php

@@ -0,0 +1,241 @@
+<?php
+
+
+namespace App\Console\Commands;
+
+
+use App\Facade\TableName;
+use App\Models\AccountsInfo;
+use App\Models\Cpf;
+use App\Models\SystemStatusInfo;
+use App\Models\Withdrawal;
+use App\Models\WithdrawalChannelPositionConfig;
+use App\Services\CashService;
+use App\Util;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+
+
+class ExemptReview extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'exempt_review';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '提现免审--定时扫表订单';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+
+        $open=(int)SystemStatusInfo::OnlyGetCacheValue(SystemStatusInfo::$KEY_WithDrawSwitch);
+        if(!$open)return;
+
+        $WithdrawalModel = new Withdrawal();
+        $OrderWithDraw = DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw')->where('State', 1)
+                           ->whereRaw('locking = 0')->orderBy('CreateDate', 'desc')->get();
+        $userIDChannelMap = AccountsInfo::whereIn('UserID', $OrderWithDraw->pluck('UserID'))
+                                        ->pluck('Channel', 'UserID');
+
+        $channelConfigs = WithdrawalChannelPositionConfig::where('status', 1)->pluck('agent', 'channel');
+
+        foreach ($OrderWithDraw as $value) {
+
+
+
+
+            if ($value->State != 1) {
+                continue;
+            }
+            // 验证免审条件
+            $verifyRes = (new \App\Services\Withdrawal())->configVerify($value->UserID, $value->WithDraw,
+                                                                        $value->OrderId);
+            if (!$verifyRes) {
+                continue;
+            }
+
+
+
+
+            //防止刷子补充
+
+            $AccountsInfoModel = new AccountsInfo();
+
+            //            $mailNums=DB::table(TableName::QPAccountsDB() . 'OrderWithDraw')
+            //                        ->selectRaw('count(distinct (UserID)) as nums')
+            //                        ->where("EmailAddress",$value->EmailAddress)
+            //                        ->first();
+            $sameCountCheck = 1;
+            $sameCountCheckIP = 1;
+
+            if ($verifyRes == 4) {
+                $sameCountCheck = 2;
+                $sameCountCheckIP = 3;
+            } elseif ($verifyRes == 5) {
+                $sameCountCheck = 10;
+                $sameCountCheckIP = 200;
+            } elseif ($verifyRes == 2) {
+                $sameCountCheck = 3;
+                $sameCountCheckIP = 100;
+            } elseif ($verifyRes == 3) {
+                $sameCountCheck = 3;
+                $sameCountCheckIP = 100;
+            }
+
+            if($verifyRes==1||$verifyRes==9){
+                Log::info('首次审核阻拦自动免审:'.$value->OrderId);
+                continue;
+            }
+            if ($AccountsInfoModel->sameWithDrawEmail($value->EmailAddress) > $sameCountCheck) {
+                Log::info('EMAIL重复过多阻拦自动免审:'.$value->OrderId.":::".$value->EmailAddress);
+                continue;
+            }
+
+            //            $nameNums=DB::table(TableName::QPAccountsDB() . 'OrderWithDraw')
+            //              ->selectRaw('count(distinct (UserID)) as nums')
+            //              ->where("EmailAddress",$value->BankUserName)
+            //              ->first();
+
+
+            //            if($AccountsInfoModel->sameWithDrawBankName($value->BankUserName)>4){
+            //                Log::info('名字重复过多阻拦自动免审:'.$value->OrderId.":::".$value->BankUserName);
+            //                continue;
+            //            }
+
+            if ($AccountsInfoModel->sameRegisterIPCountByUserID($value->UserID) > $sameCountCheckIP) {
+                Log::info('注册IP重复过多阻拦自动免审:'.$value->OrderId);
+                continue;
+            }
+
+            if ($AccountsInfoModel->sameLoginIPCount($value->UserID) > $sameCountCheckIP) {
+                Log::info('登录IP重复过多阻拦自动免审:'.$value->OrderId);
+                continue;
+            }
+            if ($AccountsInfoModel->sameLoginMacCount($value->UserID) > $sameCountCheck) {
+                Log::info('MAC地址码重复阻拦自动免审:'.$value->OrderId);
+                continue;
+            }
+            if(Cpf::getCpfCount($value->UserID)>$sameCountCheck){
+                Log::info("CPF重复阻拦自动免审:".$value->OrderId);
+                continue;
+            }
+
+
+            // 读取免审配置
+            $config = DB::table(TableName::agent().'withdrawal_position_config')->where('status', 1)->first();
+
+            if ($config) {
+                $agent = $config->agent;
+                //如果独立配置了,就走这个
+                $channelConfig = $channelConfigs[$userIDChannelMap[$value->UserID]] ?? null;
+                if ($channelConfig) {
+                    $agent = $channelConfig;
+                }
+
+                if (empty($agent)) { // 默认第一家
+                    $agent = DB::connection('write')->table('agent.dbo.admin_configs')->where('config_value', 1)
+                               ->where('type', 'cash')->where('status', 1)->first()->config_value;
+                }
+
+                // 验证用户提现方式
+                $verifyAccountWithdrawal = $WithdrawalModel->AccountWithDrawInfo($value->UserID, $agent);
+                if (!$verifyAccountWithdrawal) {
+                    Log::info('不支持的提现格式'.$value->OrderId);
+                    continue;
+                }
+
+                $redis = Redis::connection();
+                $order_sn = $value->OrderId;
+                if ($redis->exists($order_sn.'key1')) {
+                    continue;
+                }
+
+                $redis->set($order_sn.'key1', $order_sn, 3600 * 24);
+
+
+
+                $log = ['user_id'   => $value->UserID,
+                        'config_id' => $config->id,
+                        'use_quota' => $value->WithDraw,
+                        'order_sn'  => $value->OrderId];
+                DB::connection('write')->table('agent.dbo.withdrawal_position_log')
+                  ->updateOrInsert(['order_sn' => $value->OrderId], $log);
+                $state = 5;
+                $RecordData = ['admin_id'     => 0,
+                               'before_state' => 1,
+                               'after_state'  => $state,
+                               'RecordID'     => $value->RecordID,
+                               'create_at'    => date('Y-m-d H:i:s'),
+                               'update_at'    => date('Y-m-d H:i:s')];
+
+                !empty($data['remarks']) && $RecordData['remarks'] = $data['remarks'];
+                // 添加用户提现操作记录
+                DB::connection('write')->table('QPAccountsDB.dbo.AccountsRecord')
+                  ->updateOrInsert(['RecordID' => $value->RecordID, 'type' => 1], $RecordData);
+
+
+                $RecordID = $value->RecordID;
+                $amount = $value->WithDraw;
+                $accountName = $value->BankUserName;
+                $email = $value->EmailAddress;
+                $phone = $value->PhoneNumber;
+                $PixNum = $value->PixNum;
+                $PixType = $value->PixType;
+                $OrderId = $value->OrderId;
+                $IFSCNumber = $value->IFSCNumber;
+                $BranchBank = $value->BranchBank;
+                $BankNO = $value->BankNO;
+
+                if($PixType>10){
+                    Util::WriteLog("pixerror",$value);
+                    $oldRecord=DB::table('QPAccountsDB.dbo.OrderWithDraw')->where('UserID', $value->UserID)->where('State',2)->first();
+                    if(isset($oldRecord)&&isset($oldRecord->PixType)){
+                        $PixType=$oldRecord->PixType;
+                    }else{
+                        $PixType=1;
+                    }
+
+                }
+                // 改变状态处理中
+                $agentID = DB::connection('write')->table('agent.dbo.admin_configs')->where('config_value', $agent)
+                             ->select('id')->first()->id ?? '';
+
+                DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw')->where('OrderId', $OrderId)
+                  ->update(['State' => $state, 'agent' => $agentID]);
+
+                Log::info('自动免审:'.$OrderId.'--'.$agentID);
+
+                $service = CashService::payment($agent);
+                $result = $service->payment($RecordID, $amount, $accountName, $phone, $email, $OrderId, $PixNum,
+                                            $PixType, $IFSCNumber, $BranchBank, $BankNO);
+                if ($result === 'fail') {
+                    Log::info('免审提现失败:'.$value->OrderId);
+                }
+            }
+        }
+    }
+}

+ 49 - 0
app/Console/Commands/Extension.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+
+class Extension extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'extension';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '推广赚金 -- 注册-解除忽略';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        DB::connection('write')->table('agent.dbo.extension_verify')
+            ->update(['OverLook'=>0]);
+
+        return 'Success';
+    }
+
+
+}

+ 91 - 0
app/Console/Commands/OnLineMax.php

@@ -0,0 +1,91 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Facade\TableName;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+
+class OnLineMax extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'online_max';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '最高在线人数统计';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $carbon = Carbon::now();
+        $Sql = DB::table(TableName::QPPlatformDB() . 'OnLineStreamInfo');
+        $date = $carbon->format('Y-m-d');
+
+        if (Redis::exists('LastID')) {
+            $LastID = Redis::get('LastID');
+            $Sql = $Sql->where('ID', '>=', $LastID)->whereDate('InsertDateTime', $date);
+        } else {
+            $Sql = $Sql->whereDate('InsertDateTime', $date);
+        }
+
+        $maxNum = $Sql->select('ID', 'OnLineCountSum')->orderBy('OnLineCountSum', 'desc')->first();
+
+        if (!$maxNum) {
+            return false;
+        }
+
+        Redis::set('LastID', $maxNum->ID);
+
+        $first = DB::connection('write')->table(TableName::QPRecordDB() . 'RecordPlatformData')
+            ->where('DateID', $carbon->format('Ymd'))
+            ->where('Channel', -1)
+            ->first();
+
+        if ($first && $maxNum->OnLineCountSum > $first->MaxOnLine) {
+
+            DB::connection('write')->table(TableName::QPRecordDB() . 'RecordPlatformData')
+                ->where('DateID', $carbon->format('Ymd'))
+                ->where('Channel', -1)
+                ->update(['MaxOnLine' => $maxNum->OnLineCountSum]);
+        }
+
+        $lastAccountId = DB::connection('read')->table(TableName::QPAccountsDB() . 'AccountsInfo')
+            ->max('UserID');
+
+        $lastIdentifyId = DB::connection('read')->table(TableName::QPAccountsDB() . 'GameIdentifier')
+            ->max('UserID');
+        try {
+            if($lastAccountId+3000>$lastIdentifyId && $lastIdentifyId>10000){
+                $result = DB::connection('write')->select("SET NOCOUNT ON use QPAccountsDB exec GSP_GP_AddUserID 3000");
+                Log::info('自动添加用户ID'.json_encode($result));
+            }
+        }catch (\Exception $e){
+            Log::info('自动添加用户ID'.json_encode($e));
+        }
+    }
+}

+ 86 - 0
app/Console/Commands/OnlineReport.php

@@ -0,0 +1,86 @@
+<?php
+
+
+namespace App\Console\Commands;
+
+
+use App\dao\Channel\ChannelList;
+use App\Facade\TableName;
+use App\Util;
+use Illuminate\Console\Command;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\DB;
+
+
+class OnlineReport extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'online_report';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+
+//        Util::WriteLog('online_report',time());
+        $where = [];
+        $where[] = ['DateID', '=', date('Ymd')];
+        // !empty($date_end) && $where[] = ['DateID', '<=', date('Ymd', strtotime($date_end))];
+
+        $where[] = ['Channel' ,'=', '-1'];
+        $sum = implode(',', [
+            'sum(RegPeple) as RegPeple','sum(ActivePeple) as ActivePeple','sum(PayTotal) as PayTotal','sum(DrawTotal) as DrawTotal',
+        ]);
+
+        $list = DB::connection('sqlsrv')->table(TableName::QPRecordDB() . 'RecordPlatformData')
+            ->where($where)
+            ->selectRaw($sum)
+            ->first();
+
+        $line = DB::connection('read')->table('QPTreasureDB.dbo.GameScoreLocker')
+            ->selectRaw('KindID,count(DISTINCT UserID) as game_count')
+            ->whereRaw('datediff(hh,CollectDate,getdate())<=5')
+            ->groupBy('KindID')
+            ->get();
+
+        $online = json_encode($line);
+        Util::WriteLog('online_report',$list);
+        Util::WriteLog('online_report',$online);
+        if($list){
+            $data = [
+                'ldate' => date('Ymd'),
+                'online' => $online,
+                'register' => $list->RegPeple??0,
+                'active' => $list->ActivePeple??0,
+                'recharge' => $list->PayTotal??0,
+                'withdraw' => $list->DrawTotal??0,
+                'created' => time()
+            ];
+            DB::connection('mysql')->table('game-report')->insert($data);
+        }
+    }
+}

+ 44 - 0
app/Console/Commands/Opcache.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
+
+class Opcache extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'opache:reset';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'clear opcache';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        opcache_reset();
+        Log::info('cli opcache 缓存更新完成');
+    }
+}

+ 109 - 0
app/Console/Commands/PayOrder.php

@@ -0,0 +1,109 @@
+<?php
+
+
+namespace App\Console\Commands;
+
+use App\Http\AppFlyerEvent\AppflyerEvent;
+use App\Http\helper\Helper;
+use App\Http\helper\HttpCurl;
+use App\Services\StoredProcedure;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+
+class PayOrder extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'order_list';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '订单队列';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+//        $redis = new \Redis();
+//
+//        $redis->connect('127.0.0.1', '6379');
+
+//        $redis->auth('uKliY6dhGYhWAwCW');
+//        $redis->connect('127.0.0.1', '6379');
+
+        while (true) {
+
+            $res = Redis::rPop('payService');
+
+            if ($res) {
+
+                $jsonToData = \GuzzleHttp\json_decode($res, true);
+                $userID = $jsonToData['userID'];
+                $payMoney = $jsonToData['payAmt'];
+                $favorable_price = $jsonToData['favorable_price'];
+                $Score = $jsonToData['Score'];
+                $GiftsID = $jsonToData['GiftsID'];
+
+                // 开始执行时间
+                $startTime = Helper::millisecond();
+
+                // 执行存储过程 -- 防刷机制
+                StoredProcedure::SetUserTabType($userID);
+                //DB::connection('write')->select("SET NOCOUNT ON use QPAccountsDB exec GSP_GP_SetUserTabType12 $userID");
+
+                Log::info('GSP_GP_SetUserTabType12 执行时间:' . ((Helper::millisecond() - $startTime) / 1000));
+
+                # 单控标签 -- 执行存储过程
+                StoredProcedure::user_label($userID, 1, $payMoney);
+
+                Log::info('CheckAccountsLabel 执行时间:' . ((Helper::millisecond() - $startTime) / 1000));
+
+
+                // 服务器通知
+                $url = config('transfer.stock')['url'] . 'notifyPay';
+
+                $data = [
+                    'userid' => $userID,
+                    'getScore' => $favorable_price,
+                    'score' => $Score,
+                    'giftsid' => empty($GiftsID) ? 0 : $GiftsID
+                ];
+
+                (new HttpCurl())->service($url, $data);
+
+                Log::info('中转服 执行时间:' . ((Helper::millisecond() - $startTime) / 1000));
+
+                // AF 事件
+                $bool = (new AppflyerEvent())->event($userID, '', 'af_purchase_new', $payMoney);
+
+                Log::info('AF 执行时间:' . ((Helper::millisecond() - $startTime) / 1000));
+
+
+                echo '李雪峰';
+
+
+            }
+            sleep(3);
+        }
+    }
+}

+ 293 - 0
app/Console/Commands/RecordPlatformData.php

@@ -0,0 +1,293 @@
+<?php
+
+
+namespace App\Console\Commands;
+
+
+use App\Facade\TableName;
+use App\Http\helper\NumConfig;
+use App\Models\AccountsInfo;
+use App\Models\RecordScoreInfo;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class RecordPlatformData extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'RecordPlatformData';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '数据统计';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $date = Carbon::yesterday();
+//        $date = Carbon::parse('20220525');
+//        $RecordPlatformDataModel = new \App\Models\RecordPlatformData();
+
+        $dateID = $date->format('Ymd');
+
+        $service = new \App\Services\RecordPlatformData();
+
+        $dayBefore = Carbon::now()->subDays(30)->format('Ymd');
+
+        $list = DB::table(TableName::QPRecordDB() . 'RecordPlatformData')
+            ->where('DateID', '>=', $dayBefore)
+            ->get();
+        $now = Carbon::now()->format('Ymd');
+
+        foreach ($list as $value) {
+            $dateDay = $this->dateDiff($value->DateID, $now);
+            switch ($dateDay) {
+                case 2:
+                    $service->Retain($value->DateID, Carbon::parse($value->DateID)->addDay(1), 'RetainOne');
+                    $service->activeRetain($value->DateID, Carbon::parse($value->DateID)->addDay(1), 'ActiveRetainOne');
+                    $service->payRetain($value->DateID, Carbon::parse($value->DateID)->addDay(1), 'PayRetainOne');
+                    break;
+                case 3:
+                    // 注册三日留存
+                    $service->Retain($value->DateID, Carbon::parse($value->DateID)->addDay(2), 'RetainThree');
+                    // 活跃三日留存
+                    $service->activeRetain($value->DateID, Carbon::parse($value->DateID)->addDay(2), 'ActiveRetainThree');
+                    // 付费三日留存
+                    $service->payRetain($value->DateID, Carbon::parse($value->DateID)->addDay(2), 'PayRetainThree');
+                    break;
+                case 7:
+                    // 注册七日留存
+                    $service->Retain($value->DateID, Carbon::parse($value->DateID)->addDay(6), 'RetainSeven');
+                    // 活跃七日留存
+                    $service->activeRetain($value->DateID, Carbon::parse($value->DateID)->addDay(6), 'ActiveRetainSeven');
+                    // 付费七日留存
+                    $service->payRetain($value->DateID, Carbon::parse($value->DateID)->addDay(6), 'PayRetainSeven');
+                    break;
+                case 30:
+                    // 注册三十日留存
+                    $service->Retain($value->DateID, Carbon::parse($value->DateID)->addDay(29), 'RetainThirty');
+                    // 活跃三十日留存
+                    $service->activeRetain($value->DateID, Carbon::parse($value->DateID)->addDay(29), 'ActiveRetainThirty');
+                    // 付费三十日留存
+                    $service->payRetain($value->DateID, Carbon::parse($value->DateID)->addDay(29), 'PayRetainThirty');
+                    break;
+            }
+        }
+
+        // 分渠道修改
+        $this->affixation($date, $dateID);
+    }
+
+
+    public function affixation($date, $dateID)
+    {
+        $RecordPlatformDataModel = new \App\Models\RecordPlatformData();
+        $dao = new \App\dao\RecordPlatformData\RecordPlatformData();
+
+        // 申请提现金额,申请人数,申请笔数
+        $OrderWithDraw = $dao->ApplyWithDraw($date);
+        // 总申请提现金额
+        $TotalOrderWithDraw = $dao->TotalOrderWithDraw($date);
+        // 提现成功的手续费
+        $ServiceFee = $dao->WithDrawOkFree($dateID);
+        // 游戏人数
+        $GameUserCounts = $dao->GameUserCount($dateID);
+        // 新增参游人数
+        $NewGameUserCounts = $dao->NewGameUserCount($date);
+        // 赠送金币 =====  增加赠送金币
+        //$GiveGolds = RecordScoreInfo::todayLottery($date->format('Y-m-d'));
+        // 充值彩金
+//        $RechargeWinnings = $dao->RechargeWinnings($dateID);
+
+        // 分享彩金
+        //$ShareWinnings = $dao->ShareWinnings($dateID);
+        // 低保
+        //$Dibao = $dao->dibao($dateID);
+        // 付费低保
+        //$payDibao = $dao->payDibao($dateID);
+        // 签到人数、金额
+        //$signIn = $dao->signIn($dateID);
+
+
+        $list = $RecordPlatformDataModel->where('DateID', $dateID)->where('Channel', '>=', 0)->get();
+
+        foreach ($list as $value) {
+
+
+            if ($OrderWithDraw->isNotEmpty()) {
+                foreach ($OrderWithDraw as $item) {
+                    $data = ['DateID' => $dateID, 'ApplyWithdraw' => $item->WithDraw, 'ApplyWithdrawCount' => $item->UserCount, 'ApplyWithdrawBi' => $item->CountBi];
+                    // 申请提现渠道修改
+                    $dao->Update($data, $value->Channel, $item->Channel, $dateID);
+                }
+            }
+
+
+            if ($ServiceFee->isNotEmpty()) {
+                // 提现完成手续费渠道修改
+                foreach ($ServiceFee as $item) {
+                    $data = ['DateID' => $dateID, 'WithdrawFree' => $item->ServiceFee];
+
+                    $dao->Update($data, $value->Channel, $item->Channel, $dateID);
+
+                }
+            }
+
+            // 游戏人数
+            if ($GameUserCounts->isNotEmpty()) {
+
+                foreach ($GameUserCounts as $item) {
+                    $data = ['DateID' => $dateID, 'GameUserCount' => $item->UserCount];
+
+                    $dao->Update($data, $value->Channel, $item->Channel, $dateID);
+                }
+            }
+
+
+//            if ($GiveGolds->isNotEmpty()) {
+//                // 赠送金币
+//                foreach ($GiveGolds as $item) {
+//                    $data = ['DateID' => $dateID, 'GiveGold' => $item->ChangeScore];
+//
+//                    $dao->Update($data, $value->Channel, $item->Channel, $dateID);
+//
+//                }
+//            }
+
+            if ($NewGameUserCounts->isNotEmpty()) {
+                // 新增游戏人数
+                foreach ($NewGameUserCounts as $item) {
+
+                    $data = ['DateID' => $dateID, 'NewGameUserCount' => $item->UserCount];
+
+                    $dao->Update($data, $value->Channel, $item->Channel, $dateID);
+                }
+            }
+
+//            if ($RechargeWinnings->isNotEmpty()) {
+//                // 充值彩金
+//                foreach ($RechargeWinnings as $item) {
+//
+//                    $data = ['DateID' => $dateID, 'RechargeWinnings' => $item->Score];
+//
+//                    $dao->Update($data, $value->Channel, $item->Channel, $dateID);
+//                }
+//            }
+
+//            if ($ShareWinnings->isNotEmpty()) {
+//                // 分享彩金
+//                foreach ($ShareWinnings as $item) {
+//
+//                    $data = ['DateID' => $dateID, 'ShareWinnings' => $item->Score];
+//
+//                    $dao->Update($data, $value->Channel, $item->Channel, $dateID);
+//                }
+//            }
+
+//            if ($Dibao->isNotEmpty()) {
+//                // 低保彩金
+//                foreach ($Dibao as $item) {
+//
+//                    $data = ['DateID' => $dateID, 'Dibao' => $item->Score,'TotalDibaoCount'=>$item->TotalDibaoCount];
+//
+//                    $dao->Update($data, $value->Channel, $item->Channel, $dateID);
+//                }
+//            }
+
+//            if ($payDibao->isNotEmpty()) {
+//                // 付费低保人数
+//                foreach ($payDibao as $item) {
+//
+//                    $data = ['DateID' => $dateID, 'PayDibao' => $item->PayDibao];
+//
+//                    $dao->Update($data, $value->Channel, $item->Channel, $dateID);
+//                }
+//            }
+
+//            if ($signIn->isNotEmpty()) {
+//                // 付费低保人数
+//                foreach ($signIn as $item) {
+//
+//                    $data = ['DateID' => $dateID, 'SignInSum' => $item->SignInSum,'SignInCount'=>$item->SignInCount];
+//
+//                    $dao->Update($data, $value->Channel, $item->Channel, $dateID);
+//                }
+//            }
+
+
+            $ARPU = $value->ActivePeple > 0 ? number_format(($value->PayTotal / $value->ActivePeple) / NumConfig::NUM_VALUE,2,'.','') : 0;
+            $ARPPU = $value->PayPeple > 0 ? number_format(($value->PayTotal / $value->PayPeple) / NumConfig::NUM_VALUE,2,'.','') : 0;
+            // 申请提现金额 、 人数 、 笔数
+            $ApplyWithdraw = $TotalOrderWithDraw->WithDraw ?? 0;
+            $ApplyWithdrawCount = $TotalOrderWithDraw->UserCount ?? 0;
+            $ApplyWithdrawBi = $TotalOrderWithDraw->CountBi ?? 0;
+            // 提现成功手续费
+            $WithdrawFree = $ServiceFee->sum('ServiceFee');
+            $GameUserCount = $GameUserCounts->sum('UserCount');
+            $NewGameUserCount = $NewGameUserCounts->sum('UserCount');
+
+
+//            $GiveGold = $GiveGolds->sum('ChangeScore');
+//            $RechargeWinningsScore = $RechargeWinnings->sum('Score');
+//
+//
+//            $ShareWinningsScore = $ShareWinnings->sum('Score');
+//            $DibaoScore = $Dibao->sum('Score');
+//            $TotalDibaoCount = $Dibao->sum('TotalDibaoCount');
+//            $PayDibaoCont = $payDibao->sum('PayDibao');
+//            $SignInSum = $signIn->sum('SignInSum');
+//            $SignInCount = $signIn->sum('SignInCount');
+
+            $GiveGold = 0;
+            $RechargeWinningsScore = 0;
+            $ShareWinningsScore = 0;
+            $DibaoScore = 0;
+            $TotalDibaoCount = 0;
+            $PayDibaoCont = 0;
+            $SignInSum = 0;
+            $SignInCount = 0;
+
+            $data = compact('ARPPU', 'ARPU', 'ApplyWithdraw', 'WithdrawFree', 'GameUserCount', 'NewGameUserCount', 'GiveGold', 'ApplyWithdrawBi',
+                'ApplyWithdrawCount','TotalDibaoCount','SignInSum','SignInCount');
+            $data['RechargeWinnings'] = $RechargeWinningsScore;
+            $data['ShareWinnings'] = $ShareWinningsScore;
+            $data['Dibao'] = $DibaoScore;
+            $data['PayDibao'] = $PayDibaoCont;
+            $RecordPlatformDataModel->where('DateID', $dateID)->where('Channel', -1)->update($data);
+
+        }
+    }
+
+    // 日期差
+    public function dateDiff($time1, $time2)
+    {
+
+        $datetime_start = date_create(date('Y-m-d', strtotime($time1, 1)));
+
+        $datetime_end = date_create(date('Y-m-d', strtotime($time2)));
+
+        $days = date_diff($datetime_start, $datetime_end)->days;
+        return $days;
+    }
+}

+ 65 - 0
app/Console/Commands/RecordServerGameCount.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Facade\TableName;
+use App\Services\Custom;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class RecordServerGameCount extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'record_server_game_count';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '按天统计游戏人数-游戏房间去重过的用户';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+
+        $DateID = date('Ymd');
+        // 查找按天去重玩过游戏的人
+        $list = DB::connection('write')->table(TableName::QPRecordDB() . 'RecordUserGameDayCount')
+            ->where('DateID', $DateID)
+            ->selectRaw('count(UserID) UserCount,GameID,sum(Cnt) Cnt')
+            ->groupBy('GameID')
+            ->get();
+
+        foreach ($list as $value) {
+            DB::connection('write')->table(TableName::QPRecordDB() . 'RecordServerGameCount')
+                ->updateOrInsert(['DateID' => $DateID, 'GameID' => $value->GameID], ['DateID' => $DateID, 'GameID' => $value->GameID, 'Cnt' => $value->Cnt, 'UserCount' => $value->UserCount]);
+        }
+
+
+        try {
+            (new Custom())->clearUnBackUser();
+        }catch (\Exception $e){
+
+        }
+
+    }
+}

+ 59 - 0
app/Console/Commands/RecordServerGameCountYesterday.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Facade\TableName;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class RecordServerGameCountYesterday extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'record_server_game_count_yesterday';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '按天统计游戏人数-游戏房间去重过的用户';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+
+        // 今日执行昨日数据
+        $DateID = Carbon::yesterday()->format('Ymd');
+        // 查找按天去重玩过游戏的人
+        $list = DB::connection('write')->table(TableName::QPRecordDB() . 'RecordUserGameDayCount')
+            ->where('DateID', $DateID)
+            ->selectRaw('count(UserID) UserCount,GameID,sum(Cnt) Cnt')
+            ->groupBy('GameID')
+            ->get();
+
+        foreach ($list as $value) {
+            DB::connection('write')->table(TableName::QPRecordDB() . 'RecordServerGameCount')
+                ->updateOrInsert(['DateID' => $DateID, 'GameID' => $value->GameID], ['DateID' => $DateID, 'GameID' => $value->GameID, 'Cnt' => $value->Cnt, 'UserCount' => $value->UserCount]);
+        }
+
+    }
+}

+ 85 - 0
app/Console/Commands/RecordThreeGameYesterday.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Facade\TableName;
+use App\Game\GameCard;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+
+class RecordThreeGameYesterday extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'record_three_game_yesterday';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '按天统计三方游戏数据';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+
+        // 今日执行昨日数据
+        $DateID = Carbon::yesterday()->format('Ymd');
+
+        $only=GameCard::where('brand','OnlyPlay')->where('state','<>',0)->pluck('gid')->toArray();
+        $pg=GameCard::where('brand','PG')->where('state','<>',0)->pluck('gid')->toArray();
+        $pp=GameCard::where('brand','PP')->where('state','<>',0)->pluck('gid')->toArray();
+
+        $atmosfera=[71,74,85,86,87,88,89,92,93];
+
+        $platform = ['only'=>$only,'atmosfera'=>$atmosfera,'pg'=>$pg,'pp'=>$pp];
+        foreach ($platform as $pItem=>$gameType){
+            $platformRecord = [
+                'ldate' => $DateID,
+                'platform' => $pItem,
+                'all_bet' => Redis::get('platform_'.$pItem.'_bet')?:0,
+                'all_win' => Redis::get('platform_'.$pItem.'_win')?:0,
+                'current_bet' => Redis::get('platform_'.$pItem.'_bet_'.$DateID)?:0,
+                'current_win' => Redis::get('platform_'.$pItem.'_win_'.$DateID)?:0,
+                'current_play' =>Redis::get('platform_'.$pItem.'_play_'.$DateID)?:0,
+                // 'sub_detail' => ''
+            ];
+//            $gameType = ($pItem == 'only'?$only:$atmosfera);
+            if(is_array($gameType)){
+                $subData = [];
+                foreach ($gameType as $item){
+                    $betkey = 'platform_'.$pItem.'_'.$item. '_bet_' . $DateID;
+
+                    $winkey = 'platform_'.$pItem.'_'.$item. '_win_' . $DateID;
+
+                    $playkey = 'platform_'.$pItem.'_'.$item.'_play_' . $DateID;
+                    $subData[$item] = ['current_bet' => Redis::get($betkey)?:0,'current_win' =>Redis::get($winkey)?:0,'current_play' =>Redis::get($playkey)?:0 ];
+                }
+                $platformRecord['sub_detail'] = json_encode($subData);
+            }
+
+            DB::connection('mysql')->table('record_platform_data')->updateOrInsert([ 'ldate' => $DateID, 'platform' => $pItem,],$platformRecord);
+        }
+
+    }
+}

+ 112 - 0
app/Console/Commands/RecordUserScoreChangeStatistics.php

@@ -0,0 +1,112 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Facade\TableName;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Facades\DB;
+
+class RecordUserScoreChangeStatistics extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'RecordUserScoreChangeStatistics';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '用户金额变化明细按天按用户汇总';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $carBob = Carbon::yesterday()->subDays(0);
+        $date = $carBob->format('Y-m-d');
+        $dateID = $carBob->format('Ymd');
+
+        $this->RecordUserScoreChangeStatistics($date, $dateID);
+        $this->RecordUserScoreChangeTotalStatistics($date, $dateID);
+
+        return true;
+    }
+
+    // 按用户,按天分,按类型分
+    public function RecordUserScoreChangeStatistics($date, $dateID)
+    {
+        DB::connection('sqlsrv')->table(TableName::QPRecordDB() . 'RecordUserScoreChange as a')
+            ->whereDate('UpdateTime', $date)
+            ->selectRaw('sum(ChangeScore) Score,count(UserID) ScoreCount,Reason as ScoreType,UserID')
+            ->orderBy('UserID')
+            ->groupBy('UserID', 'Reason')
+//            ->get()->each(function ($query) use ($dateID) {
+//                return $query->DateID = $dateID;
+//            })->map(function ($value) {
+//                return (array)$value;
+//            })
+
+            ->chunk(100, function ($res) use ($dateID) {
+
+                $list = $res->map(function ($val) use ($dateID) {
+                    $array = (array)$val;
+                    Arr::pull($array, 'row_num');
+                    $array['DateID'] = $dateID;
+                    return $array;
+                })->toArray();
+
+                DB::table(TableName::QPRecordDB() . 'RecordUserScoreChangeStatistics')->insert($list);
+
+            });
+
+    }
+
+    // 按天、按类型分
+    public function RecordUserScoreChangeTotalStatistics($date, $dateID)
+    {
+        DB::connection('sqlsrv')->table(TableName::QPRecordDB() . 'RecordUserScoreChange as a')
+            ->whereDate('UpdateTime', $date)
+            ->selectRaw('sum(ChangeScore) Score,count(UserID) ScoreCount,Reason as ScoreType')
+            ->orderBy('Reason')
+            ->groupBy('Reason')
+            ->chunk(500, function ($res) use ($dateID) {
+
+                $list = $res->map(function ($val) use ($dateID) {
+                    $array = (array)$val;
+                    Arr::pull($array, 'row_num');
+                    $array['DateID'] = $dateID;
+                    return $array;
+                })->toArray();
+
+                DB::table(TableName::QPRecordDB() . 'RecordUserScoreChangeTotalStatistics')->insert($list);
+
+            });
+//            ->get()->each(function ($query) use ($dateID) {
+//                return $query->DateID = $dateID;
+//            })->map(function ($value) {
+//                return (array)$value;
+//            })->toArray();
+
+//        DB::table(TableName::QPRecordDB() . 'RecordUserScoreChangeTotalStatistics')
+//            ->insert($list);
+    }
+}

+ 75 - 0
app/Console/Commands/Redis/FissionCommand.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace App\Console\Commands\Redis;
+
+use App\Http\logic\api\ExtensionLogic;
+use App\Jobs\Demo1;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Redis;
+
+class FissionCommand extends Command
+{
+
+    // 1005=>TP、2030=>Rummy、2050=>Rummy两人、2060=>TPJoker玩法、2061=>TPAK玩法、2070=>Rummy10Card、2090=>Rummy两人十张
+    // 2010=>百人TP、 2012=>百人AB  暂时忽略
+
+    protected $GameIDs = [1005, 2030, 2050, 2060, 2061, 2070, 2090];
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'fission';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '裂变-游戏对局';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $logic = new ExtensionLogic();
+//        $redis = new \Redis();
+//        $redis->connect('8.129.91.11', '16973');
+//        $redis->auth('0949jfi959t*fbf0o');
+        $redis = Redis::connection('test');
+
+        echo 'Start' . "\n";
+        // 出栈
+        while (true) {
+            $brPop = $redis->brPop('gameCount', 0);
+
+            $ret = $brPop[1] ?? 0;
+            if (!empty($ret)) {
+                $ret = explode(',', $ret);
+                if (in_array($ret[1], $this->GameIDs)) {
+                    $UserID = $ret[0];
+                    $logic->invitation($UserID, 2);
+                    sleep(2);
+                }
+            }
+        }
+    }
+
+    public function handle1()
+    {
+        dispatch(new Demo1());
+    }
+}

+ 45 - 0
app/Console/Commands/Redis/PublishCommand.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace App\Console\Commands\Redis;
+
+use App\Http\Controllers\Api\SubscribeController;
+use Illuminate\Console\Command;
+
+class PublishCommand extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'publish:info';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $redis = new SubscribeController();
+        $redis->pub();
+
+    }
+}

+ 95 - 0
app/Console/Commands/Redis/SubCommand.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace App\Console\Commands\Redis;
+
+use App\Http\helper\Redis;
+use App\Http\logic\api\ExtensionLogic;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
+use function AlibabaCloud\Client\envConversion;
+
+class SubCommand extends Command
+{
+    // 1005=>TP、2030=>Rummy、2050=>Rummy两人、2060=>TPJoker玩法、2061=>TPAK玩法、2070=>Rummy10Card、2090=>Rummy两人十张
+    // 2010=>百人TP、 2012=>百人AB  暂时忽略
+
+    protected $GameIDs = [1005, 2030, 2050, 2060, 2061, 2070, 2090];
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'subcommand';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '裂变-游戏对局';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        dd(22);
+        //设置php脚本执行时间
+        set_time_limit(0);
+        //设置socket连接超时时间redis-cli -h 8.129.91.11 -p 16973 -a 0949jfi959t*fbf0o
+        //ini_set('default_socket_timeout', -1);
+        try {
+
+
+            $redis = \Illuminate\Support\Facades\Redis::connection('test');
+
+            $channels = ['gameCount'];
+
+            echo 'start' . "\n";
+
+            $redis->unsubscribe($channels);
+
+            $redis->psubscribe($channels, function ($channel, $message) {
+
+//                Log::channel('SubCommand')->info('channel: ' . $channel . ' message: ' . $message);
+                echo "channel:" . $channel . ",message:" . $message . "\n";
+//
+                $data = explode(',', $channel);
+                if (in_array($data[1], $this->GameIDs)) {
+
+                    Log::channel('SubCommand')->info('进来了,channel: ' . $channel . ' message: ' . $message);
+                    $redis = Redis::getInstance();
+                    $redis = $redis->redis_content();
+
+                    $redis->lPush('fission', $data[0]);
+
+                    $UserID = $data[0];
+                    (new ExtensionLogic())->userProfit($UserID, 0);
+//
+////                    if (isset($r['code'])) {
+////                        $msg = $r['msg'] ?? '';
+////                        Log::channel('SubCommand')->info('执行结果:' . $msg);
+////                    }
+//
+//                    Log::channel('SubCommand')->info('执行结束!!!');
+                }
+
+            });
+
+        } catch (\Exception $e) {
+            echo $e->getMessage();
+        }
+    }
+}

+ 66 - 0
app/Console/Commands/StockChange.php

@@ -0,0 +1,66 @@
+<?php
+
+
+namespace App\Console\Commands;
+
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\DB;
+
+
+class StockChange extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'stock_change';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+
+        // 过滤试玩场
+        $blacklist = [32, 33, 39];
+
+        $list = DB::table('QPPlatformDB.dbo.GameRoomInfo')
+            ->whereNotIn('ServerID', $blacklist)
+            ->get();
+
+        $stock = [];
+        foreach ($list as $value) {
+            $RoomStock = $value->RoomStock;
+            $RoomStock = $RoomStock ? explode(':', explode(';', $RoomStock)[0])[1] : 0;
+            $stock[] = ['stock' => (int)$RoomStock, 'serverID' => $value->ServerID, 'type' => 1, 'mydate' => date('Y-m-d H:i:s')];
+        }
+        // 首先把标识符取掉
+        DB::table('agent.dbo.stock_change')->whereDate('mydate', date('Y-m-d'))->update(['identifier' => 0]);
+
+        // 插入数据
+        DB::table('agent.dbo.stock_change')->insert($stock);
+
+    }
+}

+ 91 - 0
app/Console/Commands/TestCon.php

@@ -0,0 +1,91 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\dao\Pay\PayError;
+use App\Facade\RedisConnect;
+use App\Facade\TableName;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+
+class TestCon extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'test_con';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+
+        $r = (new PayError())->create(2,'pay2');
+        dd($r);
+
+        $list = DB::table(TableName::agent() . 'admin_configs')
+            ->where('type', 'pay')
+            ->where('status', 1)
+
+            ->orderBy('pay_error')
+            ->orderByDesc('sort')
+            ->pluck('name', 'id')->toArray();
+
+        dd($list);
+//        $redis = new RedisConnect();
+//        // 判断开关
+//        $open = 1;
+//        $pay_id = 2;    // 支付ID
+//
+//        // 判断支付渠道
+//        if (!$redis->redis()->exists('pay_channel')) {
+//
+//            // 插表 -- 错误支付id存表
+//
+//            DB::table(TableName::agent() . 'pay_poll')
+//                ->updateOrInsert(['id' => 1], ['error_pay_id' => $pay_id . ',']);
+//
+//            DB::table(TableName::agent() . 'admin_configs')->where('id', $id)->update(['pay_error' => 1]);
+//
+//
+//            $list = DB::table(TableName::agent() . 'admin_configs')
+//                ->where('type', 'pay')
+//                ->where('status', 1)
+//                ->orderBy('pay_error')
+//                ->orderByDesc('sort')
+//                ->pluck('name', 'id')->toArray();
+//
+//
+//
+//
+//
+//        }
+
+
+    }
+
+
+}

+ 71 - 0
app/Console/Kernel.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Console;
+
+use App\Console\Commands\DbQueue;
+use App\Console\Commands\OnlineReport;
+use App\Console\Commands\DecStock;
+use App\Console\Commands\ExemptReview;
+use App\Console\Commands\Extension;
+use App\Console\Commands\PayOrder;
+use App\Console\Commands\RecordPlatformData;
+use App\Console\Commands\RecordServerGameCount;
+use App\Console\Commands\RecordServerGameCountYesterday;
+use App\Console\Commands\RecordThreeGameYesterday;
+use App\Console\Commands\RecordUserScoreChangeStatistics;
+use Illuminate\Console\Scheduling\Schedule;
+use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
+
+class Kernel extends ConsoleKernel
+{
+    /**
+     * The Artisan commands provided by your application.
+     *
+     * @var array
+     */
+    protected $commands = [
+        ExemptReview::class,
+        PayOrder::class,
+        Extension::class,
+        RecordPlatformData::class,
+        RecordServerGameCount::class,
+        RecordServerGameCountYesterday::class,
+        RecordUserScoreChangeStatistics::class,
+        DecStock::class,
+        OnlineReport::class,
+        DbQueue::class,
+        RecordThreeGameYesterday::class
+    ];
+
+    /**
+     * Define the application's command schedule.
+     *
+     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
+     * @return void
+     */
+    protected function schedule(Schedule $schedule)
+    {
+//        $schedule->command('db_queue')->everyMinute()->description('批量处理redis队列任务');
+        $schedule->command('exempt_review')->everyMinute()->description('免审提现');
+        $schedule->command('record_server_game_count')->cron('*/15 * * * * ')->description('按天统计游戏人数');
+        $schedule->command('online_max')->cron('*/8 * * * * ')->description('最高在线人数统计');
+        $schedule->command('record_server_game_count_yesterday')->cron('05 0 * * * ')->description('按天统计游戏人数--今日执行昨日');
+        $schedule->command('RecordPlatformData')->cron('10 0 * * * ')->description('数据统计');
+        $schedule->command('RecordUserScoreChangeStatistics')->cron('03 0 * * * ')->description('用户金额变化明细按天按用户汇总');
+        $schedule->command('online_report')->everyMinute()->description('每分钟统计曲线');
+
+        $schedule->command('record_three_game_yesterday')->cron('05 0 * * * ')->description('按天统计游戏人数--今日执行昨日');
+    }
+
+    /**
+     * Register the commands for the application.
+     *
+     * @return void
+     */
+    protected function commands()
+    {
+        $this->load(__DIR__.'/Commands');
+
+//        require base_path('routes/console.php');
+    }
+}

+ 88 - 0
app/Exceptions/Handler.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace App\Exceptions;
+
+use App\Notification\DingDingRobot;
+use App\Notification\TelegramBot;
+use Exception;
+use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Request;
+
+class Handler extends ExceptionHandler
+{
+    /**
+     * A list of the exception types that are not reported.
+     *
+     * @var array
+     */
+    protected $dontReport = [
+        //
+    ];
+
+    /**
+     * A list of the inputs that are never flashed for validation exceptions.
+     *
+     * @var array
+     */
+    protected $dontFlash = [
+        'password',
+        'password_confirmation',
+    ];
+
+    /**
+     * Report or log an exception.
+     *
+     * @param  \Exception  $exception
+     * @return void
+     */
+    public function report(Exception $exception)
+    {
+        if (false&&$this->shouldReport($exception)) {
+            Log::info('Exception Happen', [
+                'error'=>$exception->getMessage(),
+                'url' => Request::url(),
+                'params' => Request::all(),
+                'trace' => $exception->getTraceAsString()
+            ]);
+
+            $env = env('APP_ENV');
+            TelegramBot::getDefault()->sendProgramNotify("api",
+                 $exception->getMessage() . "\n" .
+                    '#### url:' . Request::getRequestUri()  . "\n" .
+                    '#### params:' . json_encode(Request::all()) . "\n" .
+                    '#### ' . $exception->getTraceAsString() . "\n"
+            );
+        }
+        parent::report($exception);
+    }
+
+    /**
+     * Render an exception into an HTTP response.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Exception  $exception
+     * @return \Illuminate\Http\Response
+     */
+    public function render($request, Exception $exception)
+    {
+//        if (Config::get('app.debug') === false) {
+//            if ($request->ajax()) {
+//                $message = $exception->getMessage();
+//                $line    = $exception->getLine();
+//                $file    = $exception->getFile();
+//                $code    = $exception->getCode();
+//                return response()->json(['code' => 500, 'msg' => '请求发生错误!', 'data' => [
+//                    'code'    => $code,
+//                    'line'    => $line,
+//                    'file'    => $file,
+//                    'message' => $message,
+//                ]]);
+//            } else {
+//                return response()->view('base.404');
+//            }
+//        }
+        return parent::render($request, $exception);
+    }
+}

+ 19 - 0
app/Facade/RedisConnect.php

@@ -0,0 +1,19 @@
+<?php
+
+
+namespace App\Facade;
+
+class RedisConnect
+{
+    protected $redis;
+
+    // 链接redis
+    public function redis()
+    {
+        $this->redis = new \Redis();
+        $this->redis->connect(env('REDIS_HOST'),env('REDIS_PORT'));
+        $this->redis->auth(env('REDIS_PASSWORD'));
+
+        return $this->redis;
+    }
+}

+ 35 - 0
app/Facade/TableName.php

@@ -0,0 +1,35 @@
+<?php
+
+
+namespace App\Facade;
+
+
+use Illuminate\Support\Facades\Facade;
+
+class TableName extends Facade
+{
+    public static function agent()
+    {
+        return 'agent.dbo.';
+    }
+
+    public static function QPAccountsDB()
+    {
+        return 'QPAccountsDB.dbo.';
+    }
+
+    public static function QPPlatformDB()
+    {
+        return 'QPPlatformDB.dbo.';
+    }
+
+    public static function QPRecordDB()
+    {
+        return 'QPRecordDB.dbo.';
+    }
+
+    public static function QPTreasureDB()
+    {
+        return 'QPTreasureDB.dbo.';
+    }
+}

+ 22 - 0
app/Game/AgentBonusRecord.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+
+class AgentBonusRecord extends Model
+{
+    protected $table = 'webgame.AgentBonusRecord';
+    protected $primaryKey = 'ID';
+    public $incrementing = true;
+    protected $keyType = 'int';
+    protected $connection = 'mysql';
+    protected $fillable = [
+        'GlobalUID', 'UserID', 'Date', 'org_bonus', 'sub_bonus', 'bonus', 'rate', 'level', 'status', 'update_time','SubTotal'
+    ];
+    public $timestamps = false;
+
+    const STATUS_INIT = 0;
+    const STATUS_GET = 1;
+
+}

+ 76 - 0
app/Game/AgentCommission.php

@@ -0,0 +1,76 @@
+<?php
+// 文件路径: app/UserAgentCommission.php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Redis;
+
+class AgentCommission extends Model
+{
+    protected $table = 'webgame.AgentCommission';
+    protected $fillable = ['Level', 'BRL', 'USD','MXN','RUB','EUR', 'Rate'];
+    protected $connection='mysql';
+
+    private static $_commissionData=null;
+    // 缓存提成数据
+    public static function cacheCommissionData()
+    {
+        self::$_commissionData = self::all()->keyBy('Level')->toArray();
+        foreach (self::$_commissionData as $k => &$v) {
+            $v['BRL'] = $v[env('CONFIG_24680_CURRENCY')];
+        }
+        Redis::set('UserAgentCommission', json_encode(self::$_commissionData));
+    }
+
+    // 获取缓存中的提成数据
+    public static function getCommissionData()
+    {
+        if(self::$_commissionData)return self::$_commissionData;
+        $commissionData = Redis::get('UserAgentCommission');
+        if ($commissionData) {
+            self::$_commissionData=json_decode($commissionData, true);
+            return self::$_commissionData;
+        } else {
+            self::cacheCommissionData();
+            return self::getCommissionData();
+        }
+    }
+
+    // 根据业绩获取对应的 level 和 rate
+    public static function getLevelAndRate($performance,$DefaultCurrency=null)
+    {
+        $commissionData = self::getCommissionData();
+        if(!$DefaultCurrency)$DefaultCurrency=env('CONFIG_24680_CURRENCY');
+        //判断是否存在于数据表中的货币
+        if(!in_array($DefaultCurrency,array_keys($commissionData['1']))){
+            $DefaultCurrency='USD';
+        }
+        $lastData=$commissionData[1];
+        foreach ($commissionData as $data) {
+            if ($performance < $data[$DefaultCurrency]) {
+                return ['level' => $lastData['Level'], 'rate' => $lastData['Rate']];
+            }
+            $lastData=$data;
+        }
+
+        // 如果业绩超出表中最大值,返回最高级别
+        return ['level' => end($commissionData)['Level'], 'rate' => end($commissionData)['Rate']];
+    }
+
+    // 监听模型的保存和删除事件
+    protected static function boot()
+    {
+        parent::boot();
+
+        static::saved(function ($model) {
+            self::cacheCommissionData();
+        });
+
+        static::deleted(function ($model) {
+            self::cacheCommissionData();
+        });
+    }
+}
+

+ 103 - 0
app/Game/AgentLinks.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Str;
+use Illuminate\Support\Facades\DB;
+
+class AgentLinks extends Model
+{
+    protected $table = 'webgame.AgentLinks';
+    protected $connection='mysql';
+    protected $fillable = [
+        'GlobalUID', 'UserID', 'FPID','Channel','LinkActName', 'RedirectUrl', 'Campaign', 'Code','State', 'AddInfo',
+        'ClickNum', 'RegNum', 'PayUserNum','PayTotal','PayRewards','EarnRewards', 'CreatedAt', 'UpdatedAt'
+    ];
+    protected $primaryKey = 'LinkID'; // 设置主键为 LinkID
+    public $timestamps = false;
+
+    // 生成唯一的6位推广码
+    public static function generateUniqueCode($userId=0)
+    {
+
+        $baseCode = base_convert($userId, 10, 36); // 将用户ID转换为36进制,包含数字和小写字母
+
+        if(!$userId){
+            $baseCode= base_convert(rand(604661760, 1027924992), 10, 36);
+        }
+
+        $code = substr($baseCode, 0, 6); // 截取前6位
+
+        // 确保code唯一
+        while (self::where('Code', $code)->exists()) {
+            $code = substr( base_convert(rand(604661760, 1027924992), 10, 36), 0, 6); // 生成新码,确保唯一
+        }
+
+        return $code;
+    }
+
+    // 为新用户创建默认的 Campaign 和 Code
+    public static function createDefaultCampaign($GlobalUID, $userID=null)
+    {
+        $defaultCampaign = 'Default';
+        if(!$userID){
+            $userID=intval(explode('-', $GlobalUID)[3]);
+        }
+        $defaultCode = self::generateUniqueCode($userID);
+
+        $user=GlobalUserInfo::getGameUserInfo('GlobalUID', $GlobalUID);
+
+        if(!$user&&GlobalUserInfo::$me&&GlobalUserInfo::$me->GlobalUID==$GlobalUID){
+            $user=GlobalUserInfo::$me;
+        }
+        return self::create([
+            'GlobalUID' => $GlobalUID,
+            'UserID' => $user->UserID,
+            'Campaign' => $defaultCampaign,
+            'Channel' => $user->Channel,
+            'Code' => $defaultCode
+        ]);
+    }
+    // 为新用户创建默认的 Campaign 和 Code
+
+    public static function getDefaultCampaign($GlobalUID)
+    {
+        $link=self::query()->where('GlobalUID', $GlobalUID)->where('Campaign','Default')->first();
+        if(!$link){
+            return self::createDefaultCampaign($GlobalUID);
+        }
+//        $link=self::query()->where('GlobalUID', $GlobalUID)->where('Campaign','Default')->first();
+        return $link;
+    }
+    public static function getByCode($code)
+    {
+        return self::query()->where('Code', $code)->first();
+
+    }
+    public static function createCampaign($GlobalUID,$ActName,$Campaign)
+    {
+        $link=self::getDefaultCampaign($GlobalUID);
+
+        return self::create([
+            'GlobalUID' => $GlobalUID,
+            'UserID' => $link->UserID,
+            'Channel' => $link->Channel,
+            'Campaign' => $Campaign,
+            'LinkActName' => $ActName,
+            'Code' => self::generateUniqueCode()
+        ]);
+    }
+    public static function getCampaign($GlobalUID,$ActName='',$Campaign=null)
+    {
+        if(empty($ActName)&&!$Campaign)return self::getDefaultCampaign($GlobalUID);
+
+        if(!$Campaign)$Campaign=$ActName;
+        $link=self::query()->where('GlobalUID', $GlobalUID)->where('Campaign', $Campaign)->first();
+        if(!$link){
+            return self::createCampaign($GlobalUID,$ActName,$Campaign);
+        }
+//        $link=self::query()->where('GlobalUID', $GlobalUID)->where('Campaign', $Campaign)->first();
+        return $link;
+    }
+}

+ 19 - 0
app/Game/AgentLinksClickLog.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Game;
+
+
+use Illuminate\Database\Eloquent\Model;
+
+class AgentLinksClickLog extends Model
+{
+    protected $table = 'webgame.AgentLinksClickLog';
+    protected $connection = 'mysql';
+    protected $fillable = [
+        'LinkID', 'Country', 'City', 'Other'
+    ];
+    public $timestamps = false;
+
+    // 定义时间戳字段名称
+    const CREATED_AT = 'CreateAt';
+}

+ 109 - 0
app/Game/AgentUser.php

@@ -0,0 +1,109 @@
+<?php
+
+namespace App\Game;
+
+
+use App\Http\helper\NumConfig;
+use App\Services\ExtensionCopy;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+
+class AgentUser extends Model
+{
+    const TABLE = 'webgame.AgentUser';
+    protected $connection='mysql';
+    protected $primaryKey='GlobalUID';
+    public $incrementing = false; // 非自增主键
+    protected $keyType = 'string'; // 主键类型为字符串
+    protected $table = self::TABLE;
+    public $timestamps = false;
+
+    protected $fillable = [
+        'GlobalUID', 'UserID', 'LinkCode','LinkRewards', 'Higher1GUID', 'Higher2GUID', 'Higher3GUID', 'Higher4GUID',
+        'Higher1ID', 'Higher2ID', 'Higher3ID', 'Higher4ID', 'TotalReward1', 'TotalReward2',
+        'TotalReward3', 'TotalReward4', 'downCount1', 'downCount2', 'downCount3', 'downCount4','RewardRate','RewardLimit'
+    ];
+
+    // 推广员奖励
+    public static function reward($UserID, $amount, $orderSn)
+    {
+        $UserAgent = self::query()->where('UserID', $UserID)->first();
+
+        if ($UserAgent) {
+
+            // 上级ID
+            $Higher1ID = $UserAgent->Higher1ID;
+            // 上上级ID
+            $Higher2ID = $UserAgent->Higher2ID;
+
+            // 设置默认值
+            $DirectRebate1 = $DirectRebate2 = 0;
+
+            // 1、找出充值用户上级ID
+            if ($Higher1ID > 0) {
+                // 充值金额 * 百分比例
+                $AgentRebateRatio1 = DB::connection('write')->table('QPAccountsDB.dbo.SystemStatusInfo')->where('StatusName', 'AgentRebateRatio1')->select('StatusValue')->first()->StatusValue / NumConfig::NUM_VALUE ?? 0;
+                $DirectRebate1 = $amount * $AgentRebateRatio1;
+                $rKey = sprintf(ExtensionCopy::RECHARGE_PEOPLE_SETS, $Higher1ID);
+                Redis::sAdd($rKey, $UserID);
+                Redis::expire($rKey, 86400*60);
+                $rKey = sprintf(ExtensionCopy::RECHARGE, $Higher1ID);
+                Redis::incr($rKey, $amount);
+                Redis::expire($rKey, 86400*60);
+                self::add($Higher1ID, $Higher2ID, $DirectRebate1, $UserID, $orderSn);
+                $dailyKey = 'daily_spread_rebate_stat_'.date('Ymd');
+                Redis::incr($dailyKey, $DirectRebate1);
+                Redis::expire($dailyKey, 86400*3);
+            }
+
+
+            // 2、找出上上级ID
+            if ($Higher2ID > 0) {
+                // 充值金额 * 百分比例
+                $AgentRebateRatio2 = DB::connection('write')->table('QPAccountsDB.dbo.SystemStatusInfo')->where('StatusName', 'AgentRebateRatio2')->select('StatusValue')->first()->StatusValue / NumConfig::NUM_VALUE ?? 0;
+                $DirectRebate2 = $amount * $AgentRebateRatio2;
+                $rKey = sprintf(ExtensionCopy::RECHARGE_PEOPLE_SETS, $Higher2ID);
+                Redis::sAdd($rKey, $UserID);
+                Redis::expire($rKey, 86400*60);
+                $rKey = sprintf(ExtensionCopy::RECHARGE, $Higher2ID);
+                Redis::incr($rKey, $amount);
+                Redis::expire($rKey, 86400*60);
+                self::add($Higher2ID, 0, $DirectRebate2, $UserID, $orderSn, 2);
+                $dailyKey = 'daily_spread_rebate_stat_'.date('Ymd');
+                Redis::incr($dailyKey, $DirectRebate2);
+                Redis::expire($dailyKey, 86400*3);
+            }
+
+            // 修改推广奖励数据
+            $data = [
+                'TotalReward1' => $DirectRebate1 + $UserAgent->TotalReward1,
+                'Balance1' => $DirectRebate1 + $UserAgent->Balance1,
+                'TotalReward2' => $DirectRebate2 + $UserAgent->TotalReward2,
+                'Balance2' => $DirectRebate2 + $UserAgent->Balance2,
+            ];
+            DB::connection('write')->table('QPAccountsDB.dbo.UserAgent')->where('UserID', $UserID)->update($data);
+
+            // 增加总奖励
+            $TotalReward = $DirectRebate1 + $DirectRebate2;
+            DB::connection('write')->table('QPAccountsDB.dbo.SystemAgentReward')->increment('TotalReward', $TotalReward);
+        }
+    }
+
+    public static function add($UserID, $SpreaderID, $DirectRebate, $SourceUserID, $OrderSn, $Level = 1, $orderIds = '', $Type = 3)
+    {
+        $data = [
+            'UserID' => $UserID,
+            'SpreaderID' => $SpreaderID,
+            'DirectRebate' => $DirectRebate,
+            'SourceUserID' => $SourceUserID,
+            'OrderSn' => $OrderSn,
+            'Level' => $Level,
+            'orderIds' => $orderIds,
+            'Type' => $Type
+        ];
+        self::query()->insert($data);
+
+
+    }
+}

+ 43 - 0
app/Game/AgentUserRecord.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\DB;
+use Carbon\Carbon;
+
+class AgentUserRecord extends Model
+{
+    protected $table = 'webgame.AgentUserRecord';
+    protected $connection = 'mysql';
+    protected $fillable = [
+        'GlobalUID', 'UserID', 'Date', 'Level', 'SubTotal', 'CreatedAt', 'UpdatedAt','HigherGUID','HigherID'
+    ];
+    public $timestamps = false;
+
+    // 查找或创建当日的记录,并增加到 SubTotal
+    public static function findOrCreateAndUpdate($globalUID, $userID, $level, $amount,$HigherGUID=null,$HigherID=0)
+    {
+        $date = Carbon::now()->format('Ymd'); // 获取当前日期,格式为 YYYYMMDD
+        $record = self::where('GlobalUID', $globalUID)
+            ->where('Date', $date)
+            ->where('Level', $level);
+
+        if ($record->exists()) {
+            // 如果记录存在,增加 SubTotal
+            $record->increment('SubTotal', $amount);
+        } else {
+            // 如果记录不存在,创建新记录
+            self::create([
+                'GlobalUID' => $globalUID,
+                'UserID' => $userID,
+                'Date' => $date,
+                'Level' => $level,
+                'SubTotal' => $amount,
+                'HigherGUID' => $HigherGUID,
+                'HigherID' => $HigherID
+            ]);
+        }
+
+    }
+}

+ 75 - 0
app/Game/Banner.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+
+
+class Banner extends Model
+{
+    protected $connection='mysql';
+
+    // 指定表名,如果表名与类名的复数相同则不需要
+    protected $table = 'webgame.banners';
+
+    // 确定哪些字段可以被赋值
+    protected $fillable = ['img','img_pt','img_es', 'alt', 'link_game', 'link_module'];
+
+
+    // 隐藏不需要返回的字段
+    protected $hidden = ['img_es', 'img_pt'];
+
+    // 关闭自动维护的时间戳,如果您没有在表中创建时间戳字段则需要
+    public $timestamps = false;
+
+    public function game()
+    {
+        return $this->belongsTo(GameCard::class, 'link_game', 'id');
+    }
+
+    public function module()
+    {
+        return $this->belongsTo(PageModule::class, 'link_module', 'id');
+    }
+    // img 字段的访问器
+    public function getImgAttribute($value)
+    {
+        //处理多语言
+        $language=GlobalUserInfo::getLocale();
+
+        switch ($language) {
+            case 'es':
+                $value= $this->attributes['img_es'] ?? $this->attributes['img_pt'] ?? $value;
+                break;
+            case 'pt':
+                $value= $this->attributes['img_pt'] ?? $this->attributes['img_es'] ?? $value;
+                break;
+
+        }
+
+        $cdn_org=["cdn.ouro777.com","cdn.moeda777.com"];
+        $cdn_replace="24680.imgix.net";
+        $img_add_param="?auto=format,compress&cs=srgb&dpr=2&w=500";
+        $img_add_param="";
+        $value=str_replace($cdn_org,$cdn_replace,$value).$img_add_param;
+        $origin = $_SERVER['HTTP_ORIGIN'] ??$_SERVER['HTTP_REFERER']?? '*';
+        if (strstr($origin, "cereja")) {
+            $value=str_replace("banner/","banner_cereja/",$value);
+        }
+        return $value;
+    }
+
+    // 自定义序列化的数组形式
+    public function toArray()
+    {
+        $array = parent::toArray();
+
+        // 添加动态生成的 img 字段
+        $array['img'] = $this->img;
+
+        // 移除 img_es 和 img_pt 字段
+        unset($array['img_es'], $array['img_pt']);
+
+        return $array;
+    }
+}

+ 40 - 0
app/Game/BetBy/BetItem.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Game\BetBy;
+
+
+class BetItem
+{
+    public $id;
+    public $event_id;
+    public $sport_id;
+    public $tournament_id;
+    public $category_id;
+    public $live;
+    public $sport_name;
+    public $category_name;
+    public $tournament_name;
+    public $competitor_name;
+    public $market_name;
+    public $outcome_name;
+    public $scheduled;
+    public $odds;
+
+    public function __construct(array $data)
+    {
+        $this->id = $data['id'];
+        $this->event_id = $data['event_id'];
+        $this->sport_id = $data['sport_id'];
+        $this->tournament_id = $data['tournament_id'];
+        $this->category_id = $data['category_id'];
+        $this->live = $data['live'];
+        $this->sport_name = $data['sport_name'];
+        $this->category_name = $data['category_name'];
+        $this->tournament_name = $data['tournament_name'];
+        $this->competitor_name = $data['competitor_name'];
+        $this->market_name = $data['market_name'];
+        $this->outcome_name = $data['outcome_name'];
+        $this->scheduled = $data['scheduled'];
+        $this->odds = $data['odds'];
+    }
+}

+ 37 - 0
app/Game/BetBy/BetSlipItem.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Game\BetBy;
+
+class BetSlipItem
+{
+    public $id;
+    public $timestamp;
+    public $player_id;
+    public $operator_id;
+    public $operator_brand_id;
+    public $ext_player_id;
+    public $currency;
+    public $type;
+    public $sum;
+    public $k;
+    public $is_quick_bet;
+    public $bets;
+
+    public function __construct(array $data)
+    {
+        $this->id = $data['id'];
+        $this->timestamp = $data['timestamp'];
+        $this->player_id = $data['player_id'];
+        $this->operator_id = $data['operator_id'];
+        $this->operator_brand_id = $data['operator_brand_id'];
+        $this->ext_player_id = $data['ext_player_id'];
+        $this->currency = $data['currency'];
+        $this->type = $data['type'];
+        $this->sum = $data['sum'];
+        $this->k = $data['k'];
+        $this->is_quick_bet = $data['is_quick_bet'];
+        $this->bets = array_map(function ($bet) {
+            return new BetItem($bet);
+        }, $data['bets']);
+    }
+}

+ 51 - 0
app/Game/BetBy/BonusItem.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace App\Game\BetBy;
+
+class BonusItem
+{
+    public $id;
+    public $templateId;
+    public $name;
+    public $type;
+    public $playerId;
+    public $externalPlayerId;
+    public $brandId;
+    public $eventScheduled;
+    public $receiptDate;
+    public $issueType;
+    public $restrictions;
+    public $viewed;
+    public $activationDate;
+    public $endingDate;
+    public $status;
+    public $fromTime;
+    public $toTime;
+    public $freebetData;
+    public $comboboostData;
+
+    public function __construct(array $data)
+    {
+        $this->id = $data['id'];
+        $this->templateId = $data['template_id'];
+        $this->name = $data['name'];
+        $this->type = $data['type'];
+        $this->playerId = $data['player_id'];
+        $this->externalPlayerId = $data['external_player_id'];
+        $this->brandId = $data['brand_id'];
+        $this->eventScheduled = $data['event_scheduled'];
+        $this->receiptDate = $data['receipt_date'];
+        $this->issueType = $data['issue_type'];
+        $this->restrictions = array_map(function($restriction) {
+            return new RestrictionItem($restriction);
+        }, $data['restrictions'] ?? []);
+        $this->viewed = $data['viewed'];
+        $this->activationDate = $data['activation_date'];
+        $this->endingDate = $data['ending_date'];
+        $this->status = $data['status'];
+        $this->fromTime = $data['from_time'];
+        $this->toTime = $data['to_time'];
+        $this->freebetData = $data['freebet_data'] ? new FreebetDataItem($data['freebet_data']) : null;
+        $this->comboboostData = $data['comboboost_data'] ? new ComboboostDataItem($data['comboboost_data']) : null;
+    }
+}

+ 19 - 0
app/Game/BetBy/BonusTemplate.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Game\BetBy;
+
+class BonusTemplate
+{
+    public $templateId;
+    public $name;
+    public $description;
+    // 其他属性...
+
+    public function __construct(array $data)
+    {
+        $this->templateId = $data['template_id'];
+        $this->name = $data['name'];
+        $this->description = $data['description'];
+        // 其他属性...
+    }
+}

+ 19 - 0
app/Game/BetBy/ComboboostDataItem.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Game\BetBy;
+
+class ComboboostDataItem
+{
+    public $minOdd;
+    public $multipliers;
+    public $totalMultiplier;
+    public $isGlobal;
+
+    public function __construct(array $data)
+    {
+        $this->minOdd = $data['min_odd'];
+        $this->multipliers = $data['multipliers'];
+        $this->totalMultiplier = $data['total_multiplier'];
+        $this->isGlobal = $data['is_global'];
+    }
+}

+ 16 - 0
app/Game/BetBy/ErrorItem.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Game\BetBy;
+
+
+class ErrorItem
+{
+    public $name;
+    public $description;
+
+    public function __construct(array $data)
+    {
+        $this->name = $data['name'];
+        $this->description = $data['description'];
+    }
+}

+ 21 - 0
app/Game/BetBy/FreebetDataItem.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Game\BetBy;
+
+class FreebetDataItem
+{
+    public $type;
+    public $minSelection;
+    public $maxSelection;
+    public $minOdd;
+    public $maxOdd;
+
+    public function __construct(array $data)
+    {
+        $this->type = $data['type'];
+        $this->minSelection = $data['min_selection'];
+        $this->maxSelection = $data['max_selection'];
+        $this->minOdd = $data['min_odd'];
+        $this->maxOdd = $data['max_odd'];
+    }
+}

+ 18 - 0
app/Game/BetBy/PlayerDataItem.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Game\BetBy;
+class PlayerDataItem
+{
+    public $externalPlayerId;
+    public $currency;
+    public $amount;
+    public $forceActivated;
+
+    public function __construct(array $data)
+    {
+        $this->externalPlayerId = $data['external_player_id'];
+        $this->currency = $data['currency'];
+        $this->amount = $data['amount'];
+        $this->forceActivated = $data['force_activated'];
+    }
+}

+ 21 - 0
app/Game/BetBy/PlayerDetails.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Game\BetBy;
+
+class PlayerDetails
+{
+    public $playerId;
+    public $name;
+    public $email;
+    public $balance;
+    // 其他属性...
+
+    public function __construct(array $data)
+    {
+        $this->playerId = $data['player_id'];
+        $this->name = $data['name'];
+        $this->email = $data['email'];
+        $this->balance = $data['balance'];
+        // 其他属性...
+    }
+}

+ 16 - 0
app/Game/BetBy/PlayerSegment.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Game\BetBy;
+class PlayerSegment
+{
+    public $playerId;
+    public $segment;
+    // 其他属性...
+
+    public function __construct(array $data)
+    {
+        $this->playerId = $data['player_id'];
+        $this->segment = $data['segment'];
+        // 其他属性...
+    }
+}

+ 19 - 0
app/Game/BetBy/RestrictionItem.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Game\BetBy;
+
+class RestrictionItem
+{
+    public $sportId;
+    public $categoryId;
+    public $tournamentId;
+    public $eventId;
+
+    public function __construct(array $data)
+    {
+        $this->sportId = $data['sport_id'];
+        $this->categoryId = $data['category_id'];
+        $this->tournamentId = $data['tournament_id'];
+        $this->eventId = $data['event_id'];
+    }
+}

+ 52 - 0
app/Game/BetBy/TemplateItem.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Game\BetBy;
+
+class TemplateItem
+{
+    public $id;
+    public $name;
+    public $isActive;
+    public $maxBonusNumber;
+    public $type;
+    public $operatorId;
+    public $eventScheduled;
+    public $brandId;
+    public $fromTime;
+    public $toTime;
+    public $daysToUse;
+    /**
+     * @var RestrictionItem[]
+     */
+    public $restrictions;
+    /**
+     * @var FreebetDataItem|null
+     */
+    public $freebetData;
+    /**
+     * @var ComboboostDataItem|null
+     */
+    public $comboboostData;
+    public $descriptions;
+
+    public function __construct(array $data)
+    {
+        $this->id = $data['id'];
+        $this->name = $data['name'];
+        $this->isActive = $data['is_active'];
+        $this->maxBonusNumber = $data['max_bonus_number'];
+        $this->type = $data['type'];
+        $this->operatorId = $data['operator_id'];
+        $this->eventScheduled = $data['event_scheduled'];
+        $this->brandId = $data['brand_id'];
+        $this->fromTime = $data['from_time'];
+        $this->toTime = $data['to_time'];
+        $this->daysToUse = $data['days_to_use'];
+        $this->restrictions = array_map(function($restriction) {
+            return new RestrictionItem($restriction);
+        }, $data['restrictions']['restriction_events'] ?? []);
+        $this->freebetData = $data['freebet_data'] ? new FreebetDataItem($data['freebet_data']) : null;
+        $this->comboboostData = $data['comboboost_data'] ? new ComboboostDataItem($data['comboboost_data']) : null;
+        $this->descriptions = $data['descriptions'];
+    }
+}

+ 39 - 0
app/Game/BetBy/TransactionItem.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace App\Game\BetBy;
+
+
+class TransactionItem
+{
+    public $id;
+    public $betslip_id;
+    public $player_id;
+    public $operator_id;
+    public $operator_brand_id;
+    public $ext_player_id;
+    public $timestamp;
+    public $amount;
+    public $currency;
+    public $cross_rate_euro;
+    public $operation;
+    public $bonus_id;
+
+    public $parent_transaction_id;
+
+    public function __construct(array $data)
+    {
+        $this->id = $data['id'];
+        $this->betslip_id = $data['betslip_id'];
+        $this->player_id = $data['player_id'];
+        $this->operator_id = $data['operator_id'];
+        $this->operator_brand_id = $data['operator_brand_id'];
+        $this->ext_player_id = $data['ext_player_id'];
+        $this->timestamp = $data['timestamp'];
+        $this->amount = $data['amount'];
+        $this->currency = $data['currency'];
+        $this->cross_rate_euro = $data['cross_rate_euro'];
+        $this->operation = $data['operation'];
+        $this->bonus_id = $data['bonus_id'] ?? null;
+        $this->parent_transaction_id = $data['parent_transaction_id'] ?? null;
+    }
+}

+ 107 - 0
app/Game/BigWinner.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace App\Game;
+
+use App\Facade\TableName;
+use App\Game\Services\RouteService;
+use Illuminate\Database\Eloquent\Model;
+
+use Illuminate\Support\Facades\DB;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Redis;
+
+
+class BigWinner extends Model
+{
+    protected $connection = 'mysql';
+    protected $table = 'webgame.big_winners'; // 指定模型对应的表名
+
+    // 指定可以被批量赋值的属性
+    protected $fillable = ['img', 'gtype', 'uid', 'nick', 'lv', 'win', 'wintime'];
+
+    // 指定日期时间转换
+    protected $dates = ['wintime'];
+
+    public static function FindWinnerFromGame()
+    {
+        $rekey='check_bigwinner';
+        if(Redis::ttl($rekey)>60){
+            return;
+        }
+//        if(Redis::exists($rekey))return;
+
+        Redis::set($rekey, 1);
+        Redis::expire($rekey, 360);
+        // 获取当前时间和24小时前的时间
+        $now = Carbon::now();
+        $startTime = $now->subHours(24)->toDateTimeString();
+        $currentMonth = $now->format('Ym');
+        if($now->subHours(24)->format("Ym")!=$currentMonth&& $now->format('H')<4){
+            $currentMonth=$now->subHours(24)->format("Ym");
+        }
+
+        $scoreTableName = TableName::QPTreasureDB() . 'YN_RecordScoreInfo_' . $currentMonth;
+        $accountTableName = TableName::QPAccountsDB() . 'AccountsInfo'; // 假设 AccountInfo 表名是固定的
+
+        $sql = "
+    WITH RankedScores AS (
+        SELECT
+            rs.UserID,
+            rs.ChangeScore,
+            rs.UpdateTime,
+            ROW_NUMBER() OVER (PARTITION BY rs.UserID ORDER BY rs.ChangeScore DESC) AS rn
+        FROM $scoreTableName AS rs
+        WHERE rs.UpdateTime >= ?
+    )
+    SELECT TOP 50
+        rs.UserID as uid,
+        rs.ChangeScore as win,
+        rs.UpdateTime as wintime,
+        ai.NickName as nick,
+        ai.FaceID as img
+    FROM RankedScores AS rs
+    JOIN $accountTableName AS ai ON rs.UserID = ai.UserID
+    WHERE rs.rn = 1 and rs.ChangeScore>10
+    ORDER BY rs.ChangeScore DESC";
+        try {
+            $topUsers = DB::select($sql, [$startTime]);
+            if (count($topUsers) > 20) {
+                DB::connection('mysql')->statement('TRUNCATE TABLE ' . (new BigWinner())->table);
+            }
+            $userids = [];
+            foreach ($topUsers as &$user) {
+                $user = (array)$user;
+                if (isset($userids[$user['uid']])) continue;
+                $userids[$user['uid']] = 1;
+                $user['win'] = $user['win'] / 100;
+                $user['img'] = GlobalUserInfo::faceidToAvatar($user['img']);
+                BigWinner::query()->insert($user);
+//            (new BigWinner($user))->save();
+            }
+            self::clearCache();
+        }catch (\Exception $e) {
+            \Log::error($e);
+        }
+
+
+    }
+    public static function clearCache()
+    {
+//        $state=RouteService::getStateConfig();
+        $cacheKey = 'BigWinner24680';
+        Redis::del($cacheKey);
+    }
+    public static function getCache()
+    {
+        $cacheKey = 'BigWinner24680';
+
+        $cache=Redis::get($cacheKey);
+        if($cache)return json_decode($cache, true);
+        $gtypes=self::select('gtype')->distinct()->pluck('gtype');
+        $data=self::orderBy('wintime', 'desc')->get();
+        $cache=compact('gtypes','data');
+        Redis::set($cacheKey,json_encode($cache));
+        return $cache;
+
+    }
+}

+ 16 - 0
app/Game/Block.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Block extends Model
+{
+
+    protected $connection='mysql';
+
+    const TABLE = 'webgame.blocks';
+    protected $table = self::TABLE;
+
+    protected $fillable = ['block','column'];
+}

+ 11 - 0
app/Game/Config/GameBasicConfig.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace App\Game\Config;
+
+class GameBasicConfig
+{
+    public static $DOLLAR="R$";
+    public static $HallServer="wss://hall.24680.org:10000";
+
+    public static $ApiServer="https://br.24b.pro";
+}

+ 117 - 0
app/Game/GameCard.php

@@ -0,0 +1,117 @@
+<?php
+
+namespace App\Game;
+
+use App\Game\Config\GameBasicConfig;
+use App\Game\Services\RouteService;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Redis;
+
+class GameCard extends Model
+{
+    protected $connection='mysql';
+
+    const TABLE = 'webgame.games';
+    protected $table = self::TABLE;
+    public $timestamps = false;
+
+    protected $fillable = ['gid','img','img_pt','img_es','level_info', 'link', 'online', 'desc','brand','title','state'];
+
+    public static $OuroGameInfo_KEY="OuroGameInfos";
+    public static function setOuroGameInfo($gameInfo)
+    {
+        self::$_gameinfo=$gameInfo;
+        Redis::set(self::$OuroGameInfo_KEY,json_encode($gameInfo));
+    }
+    private static $_gameinfo=null;
+    public static function getOuroGameInfo(){
+        if(self::$_gameinfo==null){
+            if(Redis::exists(self::$OuroGameInfo_KEY)){
+                self::$_gameinfo=json_decode(Redis::get(self::$OuroGameInfo_KEY),true);
+            }
+        }
+        return self::$_gameinfo;
+
+    }
+    public static function GetGameByID($id){
+        $card=GameCard::where('id',$id)->first();
+        if($card){
+            $card=self::formatGames([$card])[0];
+        }
+        return $card;
+    }
+    public static function formatGames($games)
+    {
+
+        $cdn_org=["cdn.ouro777.com","cdn.moeda777.com"];
+        $cdn_replace="24680.imgix.net";
+        $img_add_param="?auto=format,compress&cs=srgb&dpr=2&w=140";
+        $img_add_param="";
+
+        //处理多语言
+        $locale=GlobalUserInfo::getLocale();
+        //处理房间信息
+        $ouroGameInfos=self::getOuroGameInfo();
+        foreach($games as $game){
+            //不是http开头,补齐
+            if(!strstr($game->link,"http")){
+                $game->link=GameBasicConfig::$ApiServer.$game->link;
+            }
+
+            if($game->brand=="PG"&&RouteService::isTestSite()){
+                $game->link=str_replace('pgpro','pg',$game->link);
+            }
+
+            if(isset($ouroGameInfos)&&$game->brand=="OURO777"&&!empty($game->gid)){
+                if(GlobalUserInfo::$me) {
+                    $score=GlobalUserInfo::getScoreByUserID(GlobalUserInfo::$me->UserID);
+                    if($score>=0) {
+                        if (isset($ouroGameInfos[$game->gid]) && !empty($ouroGameInfos[$game->gid])) {
+                            $game->level_info = $ouroGameInfos[$game->gid];
+                        }
+                    }
+                }
+            }
+
+//            if($locale=='en'){
+//                unset($game->img_pt,$game->img_es);
+//                continue;
+//            }
+            if($locale=="pt"&&!empty($game->img_pt)){
+                $game->img=$game->img_pt;
+            }elseif($locale=="es"){
+                if(!empty($game->img_es)){
+                    $game->img=$game->img_es;
+                }elseif (!empty($game->img_pt)){
+                    $game->img=$game->img_pt;
+                }
+            }
+            unset($game->img_pt,$game->img_es);
+
+            $game->img=str_replace($cdn_org,$cdn_replace,$game->img).$img_add_param;
+
+
+
+        }
+        return $games;
+
+    }
+
+    /**
+     * 关闭state检查
+     * @var bool
+     */
+    public static $enableStateCheck=true;
+    protected static function boot()
+    {
+        parent::boot();
+
+        if(self::$enableStateCheck) {
+            static::addGlobalScope('where', function (Builder $builder) {
+//            $builder->where('state', 1);
+                $builder->whereRaw(RouteService::getStateToWhereRaw());
+            });
+        }
+    }
+}

+ 194 - 0
app/Game/GlobalUserInfo.php

@@ -0,0 +1,194 @@
+<?php
+
+namespace App\Game;
+
+use App\Game\Services\BetbyService;
+use App\Http\helper\NumConfig;
+use App\IpLocation;
+use App\Jobs\GameTask;
+use App\Models\AccountsInfo;
+use App\Models\Treasure\GameScoreInfo;
+use App\Util;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Crypt;
+use Illuminate\Support\Facades\Redis;
+
+
+class GlobalUserInfo extends Model
+{
+    /**
+     * @var GlobalUserInfo
+     */
+    public static $me=null;
+    public static function getLocale()
+    {
+
+        if(isset(self::$me)&&!empty(self::$me)&&!empty(self::$me->DefaultLanguage)){
+            return substr(self::$me->DefaultLanguage,0,2);
+        }else{
+            return substr( @$_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2 );
+        }
+    }
+    public static function getLocaleByUserID($UserID,$default='en')
+    {
+
+        if(isset(self::$me)&&!empty(self::$me)&&!empty(self::$me->DefaultLanguage)&&self::$me->UserID==$UserID){
+            return substr(self::$me->DefaultLanguage,0,2);
+        }else{
+            $user=self::getGameUserInfo('UserID',$UserID);
+            $locale=$user->DefaultLanguage??substr( @$_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2 );
+            if(!$locale||empty($locale)){
+                $locale=env('DEFAULT_LOCALE','en');
+            }
+
+            return strstr(env('VALID_LOCALE','en,es,pt,ru'),$locale)?$locale:$default;
+        }
+    }
+    public static function UpdateLoginDate($request,$forceLogin=false)
+    {
+        if(self::$me) {
+            $UserID=self::$me->UserID;
+            $LastLogonDate = date('Y-m-d H:i:s');
+            $LastLogonIP=IpLocation::getRealIp();
+            if(!Redis::exists('UpdateLoginCheck_'.$UserID)||$forceLogin) {
+                Redis::setex('UpdateLoginCheck_'.$UserID,600,$UserID);
+                $FPID = $request->input("bfp", "");
+                GameTask::dispatch(['UpdateLogin', [$UserID, $LastLogonDate, $LastLogonIP,$FPID]]);
+                Util::WriteLog('24680dispatch', ['UpdateLogin', [$UserID, $LastLogonDate, $LastLogonIP]]);
+            }
+        }
+    }
+
+
+    protected $primaryKey = 'GlobalUID'; // Set the primary key
+    public $incrementing = false; // Disable auto-increment
+    public $timestamps = false; // If the table does not have 'created_at' and 'updated_at' columns
+
+
+    protected $connection = 'mysql';
+
+    // 指定表名,如果表名与类名的复数相同则不需要
+    protected $table = 'webgame.GlobalUserInfo';
+
+    // Fillable attributes for mass assignment
+    protected $fillable = [
+        'GlobalUID', 'UserID', 'GameID','FPID','LastFPID','ShortHashID', 'Accounts', 'Email', 'Phone', 'NickName', 'FaceID', 'LogonPass',
+        'InsurePass', 'Gender', 'RegisterDate', 'RegisterIP',
+        'RegisterLocation', 'DefaultLanguage', 'ServerRegion',
+        'ThemeColor', 'Level', 'Exp', 'UserRight',
+        'SpreaderID', 'LastLogonIP', 'LastLogonDate', 'ReferrType','Channel','GpsAdid','FavoriteGames','PwaInstalled'
+    ];
+
+    // Attributes that should be cast to native types
+    protected $casts = [
+        'Gender'     => 'integer',
+        'Level'      => 'integer',
+        'Exp'        => 'integer',
+        'UserRight'  => 'integer',
+        'ThemeColor' => 'string', // Casting enum as string
+    ];
+
+    // Custom date attributes
+    protected $dates = [
+        'RegisterDate',
+        'LastLogonDate'
+    ];
+
+    public static function faceidToAvatar($faceID)
+    {
+//        return "https://cdn.moeda777.com/24680/assets/avatar/2/avatar_$faceID.png";
+        return "https://24680.imgix.net/24680/assets/avatar/2/avatar_$faceID.png?auto=format,compress&cs=srgb";
+    }
+
+    /**
+     * @param $key 'UserID', 'GlobalUID', 'Email', 'Phone'
+     * @param $value
+     * @return GlobalUserInfo
+     */
+    public static function getGameUserInfo($key, $value)
+    {
+        return self::query()->where($key, $value)->first();
+
+    }
+
+    /**
+     * @param $key 'UserID', 'GlobalUID', 'Email', 'Phone'
+     * @param $value
+     * @return array|false
+     */
+    public static function getGameUserInfoToWeb($key, $value)
+    {
+        return self::toWebData(self::getGameUserInfo($key, $value));
+
+    }
+
+    /**
+     * @param GlobalUserInfo $user
+     * @return array|false
+     */
+    public static function toWebData(GlobalUserInfo $user,$makeBB=false)
+    {
+        $data = false;
+        if ($user) {
+            $u = $user->toArray();
+            $existKey = ['UserID', 'GameID', 'GlobalUID','Email', 'Phone','DefaultLanguage', 'NickName', 'FaceID', 'Gender', 'RegisterDate', 'RegisterLocation', 'InsurePass', 'Level', 'Exp', 'UserRight','Channel'];
+            $data = [];
+            foreach ($existKey as $key) {
+                $data[$key] = $u[$key];
+            }
+            if (!empty($data['InsurePass'])) $data['InsurePass'] = 1;
+            $data['sign'] = self::genGuuidSign($user);
+            $data['img'] = self::faceidToAvatar($data['FaceID']);
+            if (!intval($data['GameID'])) {
+                $data['GameID'] = AccountsInfo::query()->select(['GameID'])->where('UserID', $data['UserID'])->first()->GameID;
+                $user->GameID = $data['GameID'];
+                $user->update(['GameID' => $data['GameID']]);
+            }
+            $data['Score'] = self::getScoreByUserID($data['UserID']);
+            if($makeBB){
+                $data['bb']=['token'=>(new BetbyService())->getDefaultJWT($u)];
+            }
+            //intval(GameScoreInfo::query()->select(['Score'])->where('UserID', $data['UserID'])->first()->Score) / 100;
+        }
+        return $data;
+    }
+    private static $userToScores=[];
+    public static function getScoreByUserID($userID)
+    {
+        if(!isset(self::$userToScores[$userID])) {
+            $scoreObj=GameScoreInfo::query()->select(['Score'])->where('UserID', $userID)->first();
+            if($scoreObj)self::$userToScores[$userID] = intval($scoreObj->Score) / NumConfig::NUM_VALUE;
+            else self::$userToScores[$userID]=0;
+        }
+        return self::$userToScores[$userID];
+    }
+
+    public static function GlobalToUserID($GlobalUID)
+    {
+        return intval(explode('-', $GlobalUID)[3]);
+    }
+    public static function genGuuidSign($user)
+    {
+        return Crypt::encryptString($user->GlobalUID . '|' . (time() + 86400 * 365) . '|' . substr(md5($user->LogonPass), 0, 6));
+    }
+    public function addFavoGame($gameid)
+    {
+        $gids=explode(',',$this->FavoriteGames);
+        array_unshift($gids,$gameid);
+        if(count($gids)>20){
+            array_pop($gids);
+        }
+        $this->update(['FavoriteGames'=>implode(',',$gids)]);
+    }
+    public function removeFavoGame($gameid)
+    {
+        $gids=explode(',',$this->FavoriteGames);
+        foreach($gids as $key=>$gid){
+            if($gid==$gameid){
+                array_splice($gids,$key,1);
+            }
+        }
+        $this->update(['FavoriteGames'=>implode(',',$gids)]);
+
+    }
+}

+ 88 - 0
app/Game/LogGamecardClick.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+use Carbon\Carbon;
+
+class LogGamecardClick extends Model
+{
+    protected $connection = 'mysql'; // 如果使用非默认连接,指定连接名称
+    protected $table = 'webgame.log_gamecard_click';
+    public $timestamps = false; // 表中没有 Laravel 自动管理的时间戳字段
+
+    protected $fillable = [
+        'gameid',
+        'UserID',
+        'create_at'
+    ];
+
+    /**
+     * 记录日志
+     *
+     * @param int $gameid
+     * @param int $userId
+     * @return LogGamecardClick
+     */
+    public static function recordClick($gameid, $userId)
+    {
+        return self::create([
+            'gameid' => $gameid,
+            'UserID' => $userId,
+            'create_at' => Carbon::now()
+        ]);
+    }
+
+    /**
+     * 统计最近一周内根据 gameid 返回条数
+     *
+     * @return \Illuminate\Support\Collection
+     */
+    public static function getWeeklyStats()
+    {
+        $oneWeekAgo = Carbon::now()->subWeek();
+        return self::select('gameid', \DB::raw('COUNT(*) as clicks'))
+            ->where('create_at', '>=', $oneWeekAgo)
+            ->groupBy('gameid')
+            ->orderBy('clicks', 'desc')
+            ->get();
+    }
+    public static function getWeeklyStatsByBrand()
+    {
+        $oneWeekAgo = Carbon::now()->subWeek();
+
+        // 获取最近一周点击数量最多的游戏
+        $gameClicks = self::select('gameid', \DB::raw('COUNT(*) as clicks'))
+            ->where('create_at', '>=', $oneWeekAgo)
+            ->groupBy('gameid')
+            ->orderBy('clicks', 'desc')
+            ->get();
+
+        // 获取游戏详细信息
+        $gameIdsAll = $gameClicks->pluck('gameid')->toArray();
+        $games = GameCard::whereIn('id', $gameIdsAll)->where('state','<>',0)->get()->keyBy('id');
+
+        // 按品牌分类,确保每个品牌最多返回16个游戏
+        $result = [];
+
+        foreach ($gameClicks as $click) {
+            $game = $games->get($click->gameid);
+            if ($game) {
+                if (!isset($result[$game->brand])) {
+                    $result[$game->brand] = [];
+                }
+                if (count($result[$game->brand]) < 16) {
+                    $result[$game->brand][] = $game->id;
+                }
+            }
+        }
+
+        // 将结果格式化为 "brand: gameid, gameid, ..."
+        foreach ($result as $brand => $gameIds) {
+            $result[$brand] = implode(', ', $gameIds);
+        }
+        $result['ALL']=$gameIdsAll;//implode(', ', array_slice(,0,16));
+
+        return $result;
+    }
+}

+ 330 - 0
app/Game/Logics/SendCodeLogic.php

@@ -0,0 +1,330 @@
+<?php
+
+
+namespace App\Game\Logics;
+
+
+use App\Http\helper\HttpCurl;
+use App\Http\logic\api\BaseApiLogic;
+use App\Models\GamePhoneVerityCode;
+use App\Util;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+
+class SendCodeLogic extends BaseApiLogic
+{
+    const IP_DAILY_LIMIT_PREFIX = 'ip_daily_limit_';
+
+    const PHONE_DAILY_LIMIT_PREFIX = 'phone_daily_limit_';
+
+    /** @var string 每分钟频率限制key前缀 */
+    const PHONE_FREQUENT_LIMIT_PREFIX = 'phone_frequent_limit_';
+    const PHONE_SEND_LASTCONFIG_PREFIX = 'phone_send_last_';
+
+    const FREQUENT_IP_LIMIT_NUM = 20;
+
+    const FREQUENT_PHONE_LIMIT_NUM = 10;
+
+    protected $config; // 天一宏 -- 不卡
+    protected $kmiConfig; // kmi
+    protected $zrConfig; // ZhangRong
+    protected $plantsConfig; // ZhangRong
+
+    public function __construct()
+    {
+        // 天一宏 -- 不卡
+        $this->config = config('InterfaceAPI.short_message');
+
+        $this->kmiConfig = config('InterfaceAPI.kmi_message');
+        $this->zrConfig = config('InterfaceAPI.zr_message');
+        $this->plantsConfig = config('InterfaceAPI.plant_message');
+    }
+
+    public function send($phone, $ip, $UserID, $Content)
+    {
+
+        $model = new GamePhoneVerityCode();
+
+        // 生成验证码
+        $code = $this->code($phone);
+        $content = urlencode(str_replace('{code}', $code, $Content));
+
+        $appKey = $this->config['appKey'];
+        $password = $this->config['key'];
+        $time = time();
+
+        $sign = md5($appKey . $password . $time);
+
+        $data = [
+            'appId' => $this->config['appId'],
+            'numbers' => $phone,
+            'content' => $content,
+            'senderId' => $code,
+        ];
+
+
+        $model->addCode($UserID, $code, $phone, $ip);
+
+
+        $build = http_build_query($data);
+        Log::info('buka发送验证码参数 ' . $build);
+        $gateway = $this->config['gateway'] . '?' . $build;
+
+        $header = ['Sign' => $sign, 'Timestamp' =>$time, 'Api-Key' => $appKey];
+        Log::info('bukaheader-签名 ' . json_encode($header)."###".$appKey .'-'. $password .'-'. $time);
+        $HttpCurl = new HttpCurl();
+        $res = $HttpCurl->curl_get($gateway, $header);
+
+        Log::info('buka发送验证码结果:' . $res);
+
+        try {
+            $data = \GuzzleHttp\json_decode($res, true);
+        }catch (\Exception $e){
+            $data=[];
+        }
+        if (isset($data['status']) && $data['status'] == 0){
+            $this->setFrequent($ip, $phone,1);
+            return true;
+        }
+
+        $this->error = ['web.verify.send_error','Falha ao obter o código de verificação'];
+        return false;
+
+    }
+
+
+    public function zr_send($phone, $ip, $UserID, $Content)
+    {
+
+        $model = new GamePhoneVerityCode();
+
+        // 生成验证码
+        $code = $this->code($phone);
+        $content = urlencode(str_replace('{code}', $code, $Content));
+        $accesskey = $this->zrConfig['accesskey'];
+        $secret = $this->zrConfig['secret'];
+        $time = time();
+
+
+        $data = [
+            'accesskey' => $accesskey,
+            'secret' => $secret,
+            'mobile' => $phone,
+            'content' => $content,
+        ];
+
+
+        $model->addCode($UserID, $code, $phone, $ip);
+
+
+        $build = http_build_query($data);
+        Log::info('zr发送验证码参数 ' . $build);
+        $gateway = $this->zrConfig['gateway'] ;
+
+//        $header = ['sign' => $sign, 'Timestamp' =>$time, 'Api-Key' => $appKey];
+//        Log::info('header-签名 ' . json_encode($header)."###".$appKey .'-'. $password .'-'. $time);
+        $HttpCurl = new HttpCurl();
+        $res = $HttpCurl->curlPost($gateway, $build);
+
+        Log::info('zr发送验证码结果:' . $res);
+
+        $data = \GuzzleHttp\json_decode($res, true);
+        if (isset($data['status']) && $data['status'] == 0){
+            $this->setFrequent($ip, $phone,2);
+            return true;
+        }
+
+        $this->error = ['web.verify.send_error','Falha ao obter o código de verificação'];
+        return false;
+
+    }
+    public function plants_send($phone, $ip, $UserID, $Content)
+    {
+
+        $model = new GamePhoneVerityCode();
+
+        // 生成验证码
+        $code = $this->code($phone);
+        $content = urlencode(str_replace('{code}', $code, $Content));
+        $accesskey = $this->plantsConfig['appKey'];
+        $secret = $this->plantsConfig['secretKey'];
+        $time = time();
+
+
+        $data = [
+            'appkey' => $accesskey,
+            'secretkey' => $secret,
+            'phone' => $phone,
+            'content' => $content,
+        ];
+
+
+        $model->addCode($UserID, $code, $phone, $ip);
+
+
+        $build = http_build_query($data);
+        Log::info('plants发送验证码参数 ' . $build);
+        $gateway = $this->plantsConfig['gateway'] ;
+
+//        $header = ['sign' => $sign, 'Timestamp' =>$time, 'Api-Key' => $appKey];
+//        Log::info('header-签名 ' . json_encode($header)."###".$appKey .'-'. $password .'-'. $time);
+        $HttpCurl = new HttpCurl();
+        $res = $HttpCurl->curlPost($gateway, $build);
+
+        Log::info('plants发送验证码结果:' . $res);
+
+        try {
+            $data = \GuzzleHttp\json_decode(trim($res), true);
+            if (isset($data['code']) && $data['code'] == 0) {
+                $this->setFrequent($ip, $phone, 3);
+                return true;
+            }
+        }catch (\Exception $exception){
+            if (strstr($res,'"code":"0"')) {
+                $this->setFrequent($ip, $phone, 3);
+                return true;
+            }
+        }
+
+        $this->error = ['web.verify.send_error','Falha ao obter o código de verificação'];
+        return false;
+
+    }
+
+    public function kmiSend($phone, $ip, $UserID, $Content)
+    {
+        $model = new GamePhoneVerityCode();
+
+        $code = $this->code($phone);
+
+        $data = [
+            'accessKey' => $this->kmiConfig['access_key'],
+            'secretKey' => $this->kmiConfig['secret_key'],
+            //'from' => $content->TemplateID ?? '',
+            'to' => '0055' . $phone,
+            'message' => str_replace('{code}', $code, $Content),
+            'callbackUrl' => $this->kmiConfig['callbackUrl']
+        ];
+
+        Log::info('发送验证码参数 ' . \GuzzleHttp\json_encode($data));
+        $model->addCode($UserID, $code, $phone, $ip);
+
+        $gateway = $this->kmiConfig['gateway'];
+
+        $curl = new HttpCurl();
+        $res = $curl->curlPost($gateway, $data, 'json',true);
+
+        Log::info('发送验证码结果:' . $res);
+
+        $data = \GuzzleHttp\json_decode($res, true);
+        if (isset($data['code']) && $data['code'] == 200) {
+            $this->setFrequent($ip, $phone,4);
+            return true;
+        }
+
+        $this->error = 'No se pudo obtener el código de verificación';
+        return false;
+    }
+
+    // 生成验证码
+    public function code($phone)
+    {
+
+        // 如果之前发送过验证码并且还没使用,还返回之前的验证码
+        $query = DB::connection('write')->table('QPTreasureDB.dbo.GamePhoneVerityCode')
+            ->where('PhoneNum', $phone)
+            ->first();
+
+        if ($query) return $query->Code;
+
+
+        $length = $this->config['lengthNum'] ?: 4;
+
+        $characters = '0123456789';
+
+        if (!is_int($length) || $length < 0) {
+            return false;
+        }
+        $characters_length = strlen($characters) - 1;//因为字符串的顺序是从0开始的,下面的mt_rand()从0开始取,所以最后一个字符串要取到就只能减一
+        $string = '';
+        for ($i = $length; $i > 0; $i--) {
+            $string .= $characters[mt_rand(0, $characters_length)];
+        }
+
+        if ($string[0] == 0) $string[0] = mt_rand(1, 9);
+
+        return $string;
+    }
+// 验证码
+    public function sendVerify($phone, $ip, $Type, $Channel = 0)
+    {
+        $len = strlen($phone);
+        // 验证手机号格式
+        if (str_starts_with($phone, '55')) {
+            $len -= 2;
+            if ($len < 9 || $len > 11) {
+                Log::info('手机长度问题 ' . $len);
+                $this->error = ['web.verify.phone_length_invalid', 'Sorry, mobile numbers with ' . $len . ' digits are not supported'];
+                return false;
+            }
+        }
+
+        $first = DB::connection('write')->table('QPAccountsDB.dbo.AccountPhone')
+            ->where('PhoneNum', $phone)
+            ->first();
+
+        // 注册发送验证码 -- 已经绑定过不允许发送
+        if ($Type == 0) {
+            if ($first) {
+                $this->error = ['web.verify.phone_already_bound', 'The phone number has already been bound'];
+                return false;
+            }
+        } elseif ($Type == 1) { // 修改密码发送验证码 -- 判断手机号是否已经绑定
+            if (!$first) {
+                $this->error = ['web.verify.account_not_exist', 'Failed to send code, account does not exist'];
+                return false;
+            }
+        }
+
+        $redis = Redis::connection();
+
+        // 限制IP一天发10次
+        if ($redis->get(self::IP_DAILY_LIMIT_PREFIX . $ip) >= self::FREQUENT_IP_LIMIT_NUM) {
+            $this->error = ['web.verify.too_many_ip_requests', 'You have sent too many messages from this IP, sending is blocked'];
+            return false;
+        }
+
+        // 同一个手机号发送次数不能超过5次
+        if ($redis->get(self::PHONE_DAILY_LIMIT_PREFIX . $phone) >= self::FREQUENT_PHONE_LIMIT_NUM) {
+            $this->error = ['web.verify.too_many_phone_requests', 'You have sent too many messages from this phone number, sending is blocked'];
+            return false;
+        }
+
+        // 发送间隔时间
+        if ($redis->exists(self::PHONE_FREQUENT_LIMIT_PREFIX . $phone . '_' . $Type)) {
+            $this->error = ['web.verify.too_frequent_requests', 'You are sending too fast, please try again later'];
+            return false;
+        }
+
+        return apiReturnSuc();
+    }
+
+
+    /**
+     * 设置频率
+     * @param string $ip
+     * @param string $phone
+     */
+    public function setFrequent($ip, $phone,$SendCodeConfigType)
+    {
+        Redis::set(self::PHONE_SEND_LASTCONFIG_PREFIX . $phone, $SendCodeConfigType);
+        Redis::expire(self::PHONE_SEND_LASTCONFIG_PREFIX . $phone, 300);
+        Redis::set(self::PHONE_FREQUENT_LIMIT_PREFIX . $phone, 1);
+        Redis::expire(self::PHONE_FREQUENT_LIMIT_PREFIX . $phone, 118);
+        Redis::incr(self::PHONE_DAILY_LIMIT_PREFIX . $phone);
+        Redis::expireAt(self::PHONE_DAILY_LIMIT_PREFIX . $phone, strtotime('today +1 day'));
+        Redis::incr(self::IP_DAILY_LIMIT_PREFIX . $ip);
+        Redis::expireAt(self::IP_DAILY_LIMIT_PREFIX . $ip, strtotime('today +1 day'));
+    }
+}

+ 8 - 0
app/Game/PG/SpinResult.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace App\Game\PG;
+
+class SpinResult
+{
+
+}

+ 244 - 0
app/Game/PageModule.php

@@ -0,0 +1,244 @@
+<?php
+
+namespace App\Game;
+
+use App\Game\Services\RouteService;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Builder;
+class PageModule extends Model
+{
+    //
+    protected $connection='mysql';
+    public $timestamps = false; // 表中没有 Laravel 自动管理的时间戳字段
+    const TABLE = 'webgame.modules';
+    protected $table = self::TABLE;
+
+    protected $fillable = ['page_id', 'pos_index','icon','type', 'api', 'data','game_ids','tabtype','title','state'];
+
+    protected $casts = [
+        'data' => 'array',
+    ];
+
+//    public function subs()
+//    {
+//        return $this->hasMany(PageModule::class, 'parent_id','id');
+//    }
+//
+//    public function parent()
+//    {
+//        return $this->belongsTo(PageModule::class, 'parent_id','id');
+//    }
+    // 多对多关系 - 上级模块
+    public function parents()
+    {
+        return $this->belongsToMany(PageModule::class, 'webgame.module_parents', 'module_id', 'parent_id');
+    }
+
+    // 多对多关系 - 下级模块
+    public function subs()
+    {
+        return $this->belongsToMany(PageModule::class, 'webgame.module_parents', 'parent_id', 'module_id')->whereRaw(RouteService::getStateToWhereRaw());
+    }
+
+//    public function games()
+//    {
+//        return $this->belongsToMany(GameCard::class, null, 'game_ids');
+//    }
+// 添加了 state 状态过滤
+    public function games()
+    {
+        if(strlen($this->game_ids)) {
+            $gameIds = $this->parseGameIds(); // 假设这是一个解析 game_ids 字段的方法
+
+            // 将 $gameIds 转换为逗号分隔的字符串,用于 SQL 查询中的 FIELD 函数
+            $orderClause = 'FIELD(id, ' . $this->game_ids . ')';
+            $games = GameCard::whereIn('id', $gameIds)->orderByRaw($orderClause); // 只获取状态为 1 的游戏
+
+
+            return $games;
+        }
+        return null;
+    }
+
+    protected function parseGameIds()
+    {
+        // 这里简化处理,假设 game_ids 字段是逗号分隔的字符串
+        return explode(',', $this->game_ids);
+    }
+    public function banners()
+    {
+        return Banner::where('link_module',$this->id)->whereRaw(RouteService::getStateToWhereRaw());
+//        return $this->hasMany(Banner::class, 'link_module', 'id');
+    }
+    // 返回特定模块类型的结构化数据
+    public function getSpecificDataAttribute()
+    {
+        switch ($this->type) {
+            case 'ModuleAutoBanner':
+                return $this->handleAutoBanner();
+            case 'ModuleWinList':
+                return $this->handleWinList();
+            case 'ModuleSearch':
+                return $this->handleSearch();
+            case 'ModuleGameTabs':
+                return $this->handleGameTabs();
+            case 'GameTab':
+                return $this->handleTab();
+            case 'ModuleSmallGameList':
+            case 'ModuleRollSmallGameList':
+                return $this->handleSmallGameList();
+            case 'ModuleGameList':
+                return $this->handleGameList();
+            default:
+                return $this->data;
+        }
+    }
+
+    private function handleAutoBanner()
+    {
+        return [
+            'type' => $this->type,
+            'data'=>$this->banners()->get()
+            ];
+    }
+
+    private function handleWinList()
+    {
+        $data['type']=$this->type;
+        BigWinner::FindWinnerFromGame();
+        $titles=[0=>'win_list.tab_all',1=>'win_list.tab_slots',2=>'win_list.tab_sport'];
+        // 获取所有唯一的 gtype
+        $cache=BigWinner::getCache();
+        $gtypes = $cache['gtypes'];
+
+        // 为每个 gtype 检索赢家数据
+        $data['tabs']=[];
+        foreach ($gtypes as $type) {
+            $data['tabs'][]=['type'=>$type,'title'=>$titles[$type]];
+        }
+        $data['data']= $cache['data'];
+
+        return $data;
+
+    }
+
+    private function handleSearch()
+    {
+
+        return [
+            'title'=>$this->title,
+            'type' => $this->type,
+            'api' => $this->api,
+            'tabtype' =>$this->tabtype,
+            'tabs' => "",
+        ];
+    }
+
+    private function handleTab()
+    {
+        return [
+            'id'=>$this->id,
+            'title'=>$this->title,
+            'icon' => $this->icon,
+            'tabtype' =>$this->tabtype,
+            'link' => $this->link,
+        ];
+    }
+    private function handleGameTabs()
+    {
+        $data=['type'=>$this->type,'tabtype'=>$this->tabtype,'id'=>$this->id];
+        $data['data'] = $this->subs()->where('type','<>','GameTab')->get()->map(function ($sub) {
+            return $sub->getSpecificDataAttribute();
+        });
+        $data['tabs'] = $this->subs()->where('type','GameTab')->get()->map(function ($sub) {
+            return $sub->getSpecificDataAttribute();
+        });
+        return $data;
+    }
+
+    private function handleSmallGameList()
+    {
+
+        $pageSize = 16; // 从请求获取pageSize或使用默认值
+        // 获取与模块相关的游戏列表并应用分页
+        $gamesQuery = $this->games();
+        if($gamesQuery) {
+            $games = $gamesQuery->forPage(1, $pageSize)->get();
+            $games = GameCard::formatGames($games);
+            $this->data = $games;
+        }else{
+            $games=null;
+        }
+
+        return [
+            'id'=>$this->id,
+            'title'=>$this->title,
+            'icon' => $this->icon,
+            'type' => $this->type,
+            'api' => $this->api,
+            'tabtype' =>$this->tabtype,
+            'data_key' =>$this->data_key,
+            'data' => $games,
+            'link' => $this->link,
+        ];
+
+    }
+    private static  $DEFAULT_PAGE_SIZE=9;
+    public static function getDefaultPageSize()
+    {
+        self::$DEFAULT_PAGE_SIZE= ($_REQUEST['_d']??"m")=="m"?23:24;
+        return self::$DEFAULT_PAGE_SIZE;
+    }
+    private function handleGameList()
+    {
+
+        $pageSize = self::getDefaultPageSize(); // 从请求获取pageSize或使用默认值
+        $page = 1; // 从请求获取当前页或使用默认值
+
+        // 根据模块获取游戏ID列表
+        // 获取与模块相关的游戏列表并应用分页
+        $gamesQuery = $this->games();
+        $total = $gamesQuery->count();
+//        print_r([$total,$gamesQuery->toSql(),$pageSize]);die;
+        $games = $gamesQuery->forPage($page, $pageSize)->get();
+        $games=GameCard::formatGames($games);
+        $totalPage = ceil($total / $pageSize);
+        $this->data=$games;
+
+        return [
+            'id'=>$this->id,
+            'title'=>$this->title,
+            'tabtype' =>$this->tabtype,
+            'icon' => $this->icon,
+            'type' => $this->type,
+            'api' => $this->api,
+            'data' => $games,
+            'data_key' =>$this->data_key,
+            'page' => $page,
+            'pageSize' => $pageSize,
+            'total' => $total,
+            'totalPage' => $totalPage,
+        ];
+    }
+    /**
+     * 关闭state检查
+     * @var bool
+     */
+    public static $enableStateCheck=true;
+    protected static function boot()
+    {
+        parent::boot();
+
+        // 默认按照 pos_index 升序排序
+        static::addGlobalScope('order', function (Builder $builder) {
+            $builder->orderBy('pos_index', 'asc');
+        });
+
+        if(self::$enableStateCheck) {
+            static::addGlobalScope('where', function (Builder $builder) {
+//            $builder->where('state', 1);
+                $builder->whereRaw(RouteService::getStateToWhereRaw());
+            });
+        }
+    }
+}

+ 16 - 0
app/Game/QuickAccountPass.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+
+class QuickAccountPass extends Model
+{
+
+    protected $connection='mysql';
+
+    const TABLE = 'webgame.QuickAccountPass';
+    protected $table = self::TABLE;
+    public $timestamps = false;
+    protected $fillable = ['UserID','account','password'];
+}

+ 16 - 0
app/Game/QuickAccountPassStore.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+
+class QuickAccountPassStore extends Model
+{
+
+    protected $connection='mysql';
+
+    const TABLE = 'webgame.QuickAccountPassCenter';
+    protected $table = self::TABLE;
+    public $timestamps = false;
+    protected $fillable = ['account','password'];
+}

+ 55 - 0
app/Game/RePayConfig.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Redis;
+
+class RePayConfig extends Model
+{
+    protected $connection = 'mysql';
+    protected $table = 'webgame.RePayConfig';
+    protected $primaryKey = 'id';
+    public $timestamps = false;
+    protected $guarded = []; // Allow mass assignment for all fields
+
+    protected $fillable = [
+        'PayTotal',
+        'PayTimes',
+        'WithdrawTotal',
+        'LessThan',
+        'Condition',
+        'Price',
+        'Amount',
+        'Gift',
+        'TimeLimit',
+        'Status',
+    ];
+
+    public static function CacheDatas()
+    {
+        $key = 'RePayConfig';
+        if (Redis::exists($key)) {
+            $all = json_decode(Redis::get($key), true);
+        }
+        if (!isset($all) || empty($all) || !count($all)) {
+            $all = self::all()->where('Status', 1);
+            $all = json_decode(json_encode($all), true);
+            Redis::setex($key, 600, json_encode($all));
+        }
+        return $all;
+    }
+
+    protected static function boot()
+    {
+        parent::boot();
+
+        static::updated(function ($model) {
+            Redis::del('RePayConfig');
+        });
+
+        static::deleted(function ($model) {
+            Redis::del('RePayConfig');
+        });
+    }
+}

+ 15 - 0
app/Game/RedEnvelopeConfig.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Game;
+
+
+use Illuminate\Database\Eloquent\Model;
+
+class RedEnvelopeConfig extends Model
+{
+    protected $table = 'webgame.RedEnvelopeConfig';
+    protected $connection='mysql';
+    protected $fillable = ['StartTime', 'EndTime', 'MaxAmount', 'MinAmount', 'Conditions', 'Coefficient'];
+    public $timestamps = false;
+}
+

+ 15 - 0
app/Game/RedEnvelopeLog.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Game;
+
+
+use Illuminate\Database\Eloquent\Model;
+
+
+class RedEnvelopeLog extends Model
+{
+    protected $table = 'webgame.RedEnvelopeLogs';
+    protected $connection='mysql';
+    protected $fillable = ['UserID', 'ReceivedAt', 'Amount', 'Comment'];
+    public $timestamps = false;
+}

+ 47 - 0
app/Game/RouteModel.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace App\Game;
+
+use App\Game\Services\RouteService;
+use Illuminate\Database\Eloquent\Model;
+
+class RouteModel extends Model
+{
+    protected $connection='mysql';
+
+    const TABLE = 'webgame.routes';
+    protected $table = self::TABLE;
+
+    protected $fillable = [
+        'path', 'type', 'side', 'block', 'title', 'icon',
+        'fill', 'component', 'query', 'login', 'lpath', 'subs'
+    ];
+
+    // 由于subs是嵌套的,可能需要特别处理或使用访问器
+    protected $casts = [
+        'subs' => 'array'
+    ];
+    // 子路由关系
+    public function subs()
+    {
+        return $this->hasMany(RouteModel::class, 'parent_id', 'id')->whereRaw(RouteService::getStateToWhereRaw())->orderBy('index');
+    }
+
+    // 父路由关系,可选,如果你需要从子路由访问父路由
+    public function parent()
+    {
+        return $this->belongsTo(RouteModel::class, 'parent_id', 'id');
+    }
+    public function getSubsAttribute($value)
+    {
+        return array_map(function ($sub) {
+            return new self($sub);
+        }, json_decode($value, true));
+    }
+
+    public function setSubsAttribute($value)
+    {
+        $this->attributes['subs'] = json_encode($value);
+    }
+}
+

+ 229 - 0
app/Game/Services/AgentService.php

@@ -0,0 +1,229 @@
+<?php
+// 文件路径: app/Game/AgentService.php
+
+namespace App\Game\Services;
+
+use App\Game\AgentLinks;
+use App\Game\AgentUser;
+use App\Game\AgentUserRecord;
+use App\Game\GlobalUserInfo;
+use App\Http\helper\NumConfig;
+use Carbon\Carbon;
+use DB;
+use Illuminate\Http\Request;
+use Illuminate\Support\Str;
+
+class AgentService
+{
+    /**
+     * @param $GlobalUID
+     * @param $UserID
+     * @param $code
+     * @return AgentUser
+     */
+    public static function SetUserAgent($GlobalUID,$UserID,$code=null)
+    {
+        $agentUser=AgentUser::query()->where('GlobalUID',$GlobalUID)->first();
+        if($agentUser){
+            return $agentUser;
+        }
+        $higher1GUID = null;
+        $higher2GUID = null;
+        $higher3GUID = null;
+        $higher4GUID = null;
+        $higher1ID = 0;
+        $higher2ID = 0;
+        $higher3ID = 0;
+        $higher4ID = 0;
+
+        // 如果提供了分享 code
+        if ($code) {
+            $link = AgentLinks::getByCode($code);
+            if ($link) {
+                $link->increment('RegNum',1);
+                $higher1GUID = $link->GlobalUID;
+                $higher1ID = $link->UserID;
+
+                // 查询上级的上级
+                $Inviter = AgentUser::where('GlobalUID', $higher1GUID)->first();
+                if ($Inviter) {
+                    $higher2GUID = $Inviter->Higher1GUID;
+                    $higher2ID = $Inviter->Higher1ID;
+                    if($higher2GUID){
+                        AgentUser::query()->where("GlobalUID",$higher2GUID)->increment('downCount2',1);
+                    }
+
+                    $higher3GUID = $Inviter->Higher2GUID;
+                    $higher3ID = $Inviter->Higher2ID;
+
+                    if($higher3GUID){
+                        AgentUser::query()->where("GlobalUID",$higher3GUID)->increment('downCount3',1);
+                    }
+
+                    $higher4GUID = $Inviter->Higher3GUID;
+                    $higher4ID = $Inviter->Higher3ID;
+
+                    if($higher4GUID){
+                        AgentUser::query()->where("GlobalUID",$higher4GUID)->increment('downCount4',1);
+                    }
+                }else{
+                    $Inviter=AgentUser::create(['GlobalUID' => $higher1GUID,
+                                       'UserID' => $higher1ID]);
+                    $Inviter->increment('downCount1',1);
+
+                }
+            }
+        }
+
+        // 插入 AgentUser 记录
+        $agentUser=AgentUser::create([
+            'GlobalUID' => $GlobalUID,
+            'UserID' => $UserID,
+            'LinkCode' => $code ?? '',
+            'Higher1GUID' => $higher1GUID,
+            'Higher2GUID' => $higher2GUID,
+            'Higher3GUID' => $higher3GUID,
+            'Higher4GUID' => $higher4GUID,
+            'Higher1ID' => $higher1ID,
+            'Higher2ID' => $higher2ID,
+            'Higher3ID' => $higher3ID,
+            'Higher4ID' => $higher4ID,
+            'downCount1' => 0,
+            'downCount2' => 0,
+            'downCount3' => 0,
+            'downCount4' => 0,
+        ]);
+
+        // 为新用户创建默认的 Campaign 和 Code
+        AgentLinks::getDefaultCampaign($GlobalUID);
+        return $agentUser;
+    }
+    public static function recordPerformance($userId, $amount)
+    {
+
+        // 获取用户的邀请链
+        $user = AgentUser::query()->where('UserID', $userId)->first();
+
+
+        if(!$user){
+            $guser=GlobalUserInfo::getGameUserInfo('UserID',$userId);
+            if(!$guser)return;
+            $user=self::SetUserAgent($guser->GlobalUID,$userId);
+        }
+
+        $invitationChain= [];
+        if($user->Higher1ID)$invitationChain[]=[$user->Higher1GUID,$user->Higher1ID];
+        if($user->Higher2ID)$invitationChain[]=[$user->Higher2GUID,$user->Higher2ID];
+        if($user->Higher3ID)$invitationChain[]=[$user->Higher3GUID,$user->Higher3ID];
+        if($user->Higher4ID)$invitationChain[]=[$user->Higher4GUID,$user->Higher4ID];
+        $hasReward=$user->TotalReward;
+
+        // 更新 UserAgent 表
+        if($user->Higher1GUID) {
+            $user->increment('TotalReward', $amount);
+        }
+
+        // 遍历上级邀请者并更新业绩
+        foreach ($invitationChain as $level => [$Guid,$Uid]) {
+
+            $_real_level=$level+1;
+            $HigerGUID=null;
+            $HigerID=0;
+            if($_real_level<count($invitationChain)){
+                [$HigerGUID,$HigerID]=$invitationChain[$_real_level];
+            }
+
+            // 记录到 AgentUserRecord 表
+           AgentUserRecord::findOrCreateAndUpdate($Guid,$Uid,$_real_level, $amount,$HigerGUID,$HigerID);
+
+
+        }
+//        1、每个用户充值后,您可以获得1R$
+//        2、当用户充值总额超过100R$,他们被视为优质用户,每个优质用户你可以获得5R$
+//        3、您将收到所有用户存入金额1%的现金返还,每个用户最多可以获得100R$
+        if($user->Higher1GUID){
+            $parentUser=AgentUser::query()->where('GlobalUID', $user->Higher1GUID)->first();
+            //$parentUser->RewardLimit  默认值为100,可以调节
+            if($parentUser&&$user->LinkRewards<$parentUser->RewardLimit*NumConfig::NUM_VALUE) {
+
+
+                //找到具体活动链接
+                if ($user->LinkCode) {
+                    $link = AgentLinks::getByCode($user->LinkCode);
+                } else {
+                    $link = AgentLinks::getDefaultCampaign($user->Higher1GUID);
+                }
+                //新付费
+                if ($hasReward == 0) {
+                    $link->increment('PayUserNum', 1);
+                    $link->increment('PayRewards', 1 * NumConfig::NUM_VALUE);
+                    $user->increment('LinkRewards', 1 * NumConfig::NUM_VALUE);
+                }
+                //超过100R$
+                if ($hasReward < 100 * NumConfig::NUM_VALUE && $hasReward + $amount >= 100 * NumConfig::NUM_VALUE) {
+                    $link->increment('PayRewards', 5 * NumConfig::NUM_VALUE);
+                    $user->increment('LinkRewards', 5 * NumConfig::NUM_VALUE);
+                }
+                //1% $user->RewardRate 默认值100 /10000 =1%
+                $link->increment('PayTotal', $amount);
+                $link->increment('PayRewards', $amount *$user->RewardRate / 10000);
+                $user->increment('LinkRewards', $amount *$user->RewardRate / 10000);
+            }
+        }
+    }
+
+
+
+    public static function getShareLink($GlobalUID=null,$ActName='Default')
+    {
+        if(!$GlobalUID){
+            if(GlobalUserInfo::$me){
+                $GlobalUID=GlobalUserInfo::$me->GlobalUID;
+            }else{
+                $GlobalUID='';
+            }
+        }
+        if($ActName=='WheelFree100') {
+            $link = 'https://240.lol/cw/' . self::encodeAct($GlobalUID, $ActName);
+        }else{
+            $link = 'https://240.lol/' . self::encodeAct($GlobalUID, $ActName);
+        }
+        return $link;
+    }
+    public static function encodeAct($GlobalUID,$ActName)
+    {
+        if(empty($GlobalUID))return Str::random(6);
+        $link=AgentLinks::getCampaign($GlobalUID,$ActName);
+        return $link->Code;
+
+    }
+
+    /**
+     * @param Request $request
+     * @return AgentLinks|int
+     */
+    static public function decodeAct(Request $request)
+    {
+        $act = $request->input("act","");
+        if(empty($act)){
+            return 0;
+        }
+//        $act=self::customDecrypt($act);
+//        $act=explode("|",$act);
+//        if(count($act)<2){
+//            return -1;
+//        }
+//        [$ActName,$SpreadID,$SpreadGUID]=$act;
+        $agentLink=AgentLinks::getByCode($act);
+        if(!$agentLink)return -1;
+
+        return $agentLink;
+    }
+
+
+    private static function getLevelName($level)
+    {
+        $levels = ['top', 'second', 'third', 'fourth'];
+        return $levels[$level] ?? 'unknown';
+    }
+}

+ 110 - 0
app/Game/Services/AgentSystemService.php

@@ -0,0 +1,110 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\dao\Estatisticas\RechargeWithDraw;
+use App\Models\PrivateMail;
+use App\Models\RecordUserDataStatistics;
+use App\Services\LogDayStatisticalByDayAndChannel;
+use App\Services\StoredProcedure;
+use App\Util;
+use GuzzleHttp\Client;
+use Illuminate\Support\Facades\DB;
+
+class AgentSystemService
+{
+    public static function callAgentBackofficeApi($postData,$path='/agent_notify')
+    {
+        $apiurl = 'http://agent.24680.org';
+
+
+        $client = new Client();
+
+        $response = $client->post($apiurl . $path, [
+            'verify'=>false,
+            'form_params' => $postData, // 传递 POST 数据//                'query' => $getData, // 传递 GET 数据
+        ]);
+        $res = json_decode($response->getBody(), true);
+//            Util::WriteLog('subserver',$res);
+        return $res;
+    }
+    public static function FailWithdraw($orderWithDraw){
+        self::callAgentBackofficeApi(['agentID'=>$orderWithDraw->BankNO,'sn'=>$orderWithDraw->BranchBank,'status'=>4],'/agent_notify');
+    }
+    public static function FinishWithdraw($orderWithDraw)
+    {
+        $query=$orderWithDraw;
+        $UserID=$orderWithDraw->UserID;
+        Util::WriteLog('AgentSystem','AgentSystem提现成功');
+        $now = now();
+        $withdraw_data = [
+            'State' => 2,
+            'agent' => 6666,
+            'finishDate' => $now
+        ];
+
+        $TakeMoney = $orderWithDraw->WithDraw + $orderWithDraw->ServiceFee;
+        $OrderId=$orderWithDraw->OrderId;
+        // 增加提现记录
+        $first = DB::connection('write')->table('QPAccountsDB.dbo.UserTabData')->where('UserID', $UserID)->first();
+        if ($first) {
+            DB::connection('write')->table('QPAccountsDB.dbo.UserTabData')->where('UserID', $UserID)->increment('TakeMoney', $TakeMoney);
+        } else {
+            DB::connection('write')->table('QPAccountsDB.dbo.UserTabData')->insert(['TakeMoney' => $TakeMoney, 'UserID' => $UserID]);
+            try {
+                PrivateMail::praiseSendMail($UserID);
+            }catch (\Exception $e){
+
+            }
+        }
+
+
+        // 免审的时候,修改免审状态
+        $withdrawal_position_log = DB::connection('write')->table('agent.dbo.withdrawal_position_log')->where('order_sn', $OrderId)->first();
+        if ($withdrawal_position_log) {
+            DB::connection('write')->table('agent.dbo.withdrawal_position_log')->where('order_sn', $OrderId)->update(['take_effect' => 2, 'update_at' => date('Y-m-d H:i:s')]);
+        }
+
+        try {
+            StoredProcedure::addPlatformData($UserID, 4, $TakeMoney);
+        }catch (\Exception $exception){
+            Util::WriteLog('StoredProcedure',$exception);
+        }
+
+
+        $ServiceFee = $orderWithDraw->ServiceFee;
+        // 增加用户提现值
+        RecordUserDataStatistics::updateOrAdd($UserID, $TakeMoney, 0, $ServiceFee);
+
+        // 给用户发邮件
+        //PrivateMail::successMail($UserID, $OrderId, $TakeMoney);
+
+        //StoredProcedure::addPlatformData($UserID, 4, $TakeMoney);
+
+        // 数据统计后台 -- 提现记录添加
+        (new RechargeWithDraw())->withDraw($UserID, $TakeMoney);
+        $RecordData = [
+            'before_state' => $query->State,
+            'after_state' => $withdraw_data['State'] ?? 0,
+            'RecordID' => $query->RecordID,
+            'update_at' => date('Y-m-d H:i:s')
+        ];
+
+        // 添加用户提现操作记录
+        DB::connection('write')->table('QPAccountsDB.dbo.AccountsRecord')->updateOrInsert(['RecordID' => $query->RecordID, 'type' => 1], $RecordData);
+//        DB::connection('write')->table('QPAccountsDB.dbo.withdraw_notify')->updateOrInsert(['order_sn' => $OrderId], $notify_data);
+        DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw')->where('OrderId', $query->OrderId)->update($withdraw_data);
+
+        if (isset($withdraw_data['State']) && $withdraw_data['State'] == 2) {
+            // 单控标签
+//            StoredProcedure::user_label($UserID, 2, $TakeMoney);
+            // 渠道后台埋点
+            (new LogDayStatisticalByDayAndChannel())->updateData($UserID, 2);
+        }
+
+        self::callAgentBackofficeApi(['agentID'=>$orderWithDraw->BankNO,'sn'=>$orderWithDraw->BranchBank,'status'=>2],'/agent_notify');
+
+    }
+
+
+}

+ 442 - 0
app/Game/Services/AtmosferaService.php

@@ -0,0 +1,442 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Util;
+use GuzzleHttp\Client;
+
+class AtmosferaService
+{
+    protected $client;
+    protected $baseUrl;
+    protected $privateKey;
+
+    protected $contractor;
+
+    public function __construct()
+    {
+        $this->client = new Client();
+        $this->baseUrl = "https://atmob2b.net";
+        $this->privateKey = "PqcOolNF5gusaMiQGxzOIrSey3qp0Adw";
+        $this->contractor = 105;
+//        $this->baseUrl = "https://api-bb.igramba.com:30445";
+//        $this->privateKey = "c48BgNi8p8xsebe0wGMGhICfDeyo0Xzx";
+//        $this->contractor = 105;
+    }
+
+    // 生成SHA256签名
+    protected function generateSignature($time, $data)
+    {
+        return hash('sha256', $time . json_encode($data) . $this->privateKey);
+    }
+
+    public function checkSignature($post)
+    {
+        Util::WriteLog('24680_atmosfera',$post);
+        return true;
+        $hash =  hash('sha256', $post['time'] . $post['data'] . $this->privateKey);
+        return $hash === $post['hash'];
+    }
+
+    // 发送POST请求
+    protected function postRequest($endpoint, $time, $data)
+    {
+        $hash = $this->generateSignature($time, $data);
+        $post = [
+            'contractor' => $this->contractor,
+            'version' => 3,
+            'time' => $time,
+            'data' => json_encode($data),
+            'hash' => $hash
+        ];
+//        echo $this->baseUrl . $endpoint;
+//        echo "\r\n";
+//        echo "\r\n";
+//        echo json_encode($post);
+//        echo "\r\n";
+//        echo "\r\n";
+        Util::WriteLog('24680_atmosfera_req', $this->baseUrl . $endpoint);
+        Util::WriteLog('24680_atmosfera_req', $post);
+        $rs = Util::curlPost2($this->baseUrl . $endpoint,$post,true);
+        Util::WriteLog('24680_atmosfera_req', $rs);
+        return json_decode($rs, true);
+    }
+
+    /**
+     * 获取玩家余额
+     *
+     * @param string $userId 玩家ID,必须是先前传递给create_user_ex方法的值
+     * @param int $merchantId 商户ID
+     * @param string|null $sessionId 会话ID,可选
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {
+     *     "merchant_id": 0,
+     *     "user_id": "123654"
+     *   },
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function getBalance($userId, $merchantId, $sessionId = null)
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [
+            'user_id' => $userId,
+            'merchant_id' => $merchantId,
+            'session_id' => $sessionId
+        ];
+
+        return $this->postRequest('/get_balance', $time, $data);
+    }
+
+    /**
+     * 获取玩家账户详情
+     *
+     * @param string $userId 玩家ID,必须是先前传递给create_user_ex方法的值
+     * @param int $merchantId 商户ID
+     * @param string|null $sessionId 会话ID,可选
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {
+     *     "merchant_id": 0,
+     *     "user_id": "123654"
+     *   },
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function getAccountDetails($userId, $merchantId, $sessionId = null)
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [
+            'user_id' => $userId,
+            'merchant_id' => $merchantId,
+            'session_id' => $sessionId
+        ];
+
+        return $this->postRequest('/get_account_details', $time, $data);
+    }
+
+    /**
+     * 提现
+     *
+     * @param string $userId 玩家ID,必须是先前传递给create_user_ex方法的值
+     * @param string $transactionId 在ATMOSFERA端的唯一事务标识
+     * @param string $currency 货币ISO代码
+     * @param float $amount 提现金额
+     * @param float $bonusAmount 从玩家的奖金账户中提现的金额
+     * @param int $gameType 游戏标识符
+     * @param string $gameId 游戏回合标识符
+     * @param int $merchantId 商户ID
+     * @param string|null $sessionId 会话ID,可选
+     * @param bool $bonusGame 如果为true,bonusAmount大于0且amount等于0
+     * @param array $betData 包含下注信息的JSON编码数组
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {
+     *     "user_id": "123654",
+     *     "transaction_id": "123456_0",
+     *     "currency": "EUR",
+     *     "amount": 0.25,
+     *     "bonus_amount": 0,
+     *     "game_type": 70,
+     *     "game_id": 1234,
+     *     "merchant_id": 1,
+     *     "bonus_game": false,
+     *     "bet_data": [
+     *       {"bet_id": 123, "nominal": 0.15, "content": "{\"numbers\":[34],\"pos\":\"34\"}"},
+     *       {"bet_id": 124, "nominal": 0.1, "content": "{\"numbers\":[33],\"pos\":\"33\"}"}
+     *     ]
+     *   },
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function withdraw($userId, $transactionId, $currency, $amount, $bonusAmount, $gameType, $gameId, $merchantId, $sessionId = null, $bonusGame = false, $betData = [])
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [
+            'user_id' => $userId,
+            'transaction_id' => $transactionId,
+            'currency' => $currency,
+            'amount' => $amount,
+            'bonus_amount' => $bonusAmount,
+            'game_type' => $gameType,
+            'game_id' => $gameId,
+            'merchant_id' => $merchantId,
+            'session_id' => $sessionId,
+            'bonus_game' => $bonusGame,
+            'bet_data' => $betData
+        ];
+
+        return $this->postRequest('/withdraw', $time, $data);
+    }
+
+    /**
+     * 存款
+     *
+     * @param string $userId 玩家ID,必须是先前传递给create_user_ex方法的值
+     * @param string $transactionId 在ATMOSFERA端的唯一事务标识
+     * @param string $currency 货币ISO代码
+     * @param float $amount 存款金额
+     * @param string $startOperationId 在withdraw方法中接收到的操作ID
+     * @param int $gameType 游戏标识符
+     * @param string $gameId 游戏回合标识符
+     * @param string $gameData 包含游戏结果信息的字符串或对象
+     * @param int $merchantId 商户ID
+     * @param string|null $sessionId 会话ID,可选
+     * @param bool $bonusGame 如果为true,表示奖金游戏
+     * @param array $betData 包含下注信息的JSON编码数组
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {
+     *     "user_id": "123654",
+     *     "transaction_id": "123457_0",
+     *     "start_operation_id": "123",
+     *     "currency": "EUR",
+     *     "amount": 1,
+     *     "game_type": 70,
+     *     "game_id": 1234,
+     *     "merchant_id": 1,
+     *     "game_data": "{\"balls\":[3],\"cards\":null}",
+     *     "bonus_game": false,
+     *     "bet_data": [
+     *       {"bet_id": 123, "win_amount": 1, "jp_amount": 0},
+     *       {"bet_id": 124, "win_amount": 0, "jp_amount": 0}
+     *     ]
+     *   },
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function deposit($userId, $transactionId, $currency, $amount, $startOperationId, $gameType, $gameId, $gameData, $merchantId, $sessionId = null, $bonusGame = false, $betData = [])
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [
+            'user_id' => $userId,
+            'transaction_id' => $transactionId,
+            'currency' => $currency,
+            'amount' => $amount,
+            'start_operation_id' => $startOperationId,
+            'game_type' => $gameType,
+            'game_id' => $gameId,
+            'game_data' => $gameData,
+            'merchant_id' => $merchantId,
+            'session_id' => $sessionId,
+            'bonus_game' => $bonusGame,
+            'bet_data' => $betData
+        ];
+
+        return $this->postRequest('/deposit', $time, $data);
+    }
+
+    /**
+     * 回滚
+     *
+     * @param string $userId 玩家ID,必须是先前传递给create_user_ex方法的值
+     * @param string $transactionId 需要回滚的提现事务的唯一标识符
+     * @param int $merchantId 商户ID
+     * @param string|null $sessionId 会话ID,可选
+     * @param bool $bonusGame 如果为true,表示奖金游戏
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {
+     *     "user_id": "123654",
+     *     "transaction_id": "123457_0",
+     *     "merchant_id": 1
+     *   },
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function rollback($userId, $transactionId, $merchantId, $sessionId = null, $bonusGame = false)
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [
+            'user_id' => $userId,
+            'transaction_id' => $transactionId,
+            'merchant_id' => $merchantId,
+            'session_id' => $sessionId,
+            'bonus_game' => $bonusGame
+        ];
+
+        return $this->postRequest('/rollback', $time, $data);
+    }
+
+    // 调用API方法:check_user_ex
+    /**
+     * 调用API方法:check_user_ex
+     *
+     * @param string $extUserId 集成商系统中的用户ID
+     * @param int|null $merchantId 集成商系统中的商户ID,可选
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {
+     *     "ext_user_id": "test"
+     *   },
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function checkUserEx($extUserId, $merchantId = 0)
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [
+            'ext_user_id' => strval($extUserId),
+            'merchant_id' => $merchantId
+        ];
+
+        return $this->postRequest('/check_user_ex', $time, $data);
+    }
+
+    // 调用API方法:create_user_ex
+    /**
+     * 调用API方法:create_user_ex
+     *
+     * @param string $extUserId 集成商系统中的用户ID
+     * @param int|null $merchantId 集成商系统中的商户ID,可选
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {
+     *     "ext_user_id": "test"
+     *   },
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function createUserEx($extUserId, $merchantId = 0)
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [
+            'ext_user_id' => strval($extUserId),
+            'merchant_id' => $merchantId
+        ];
+
+        return $this->postRequest('/create_user_ex', $time, $data);
+    }
+
+    // 调用API方法:register_token_ex
+    /**
+     * 调用API方法:register_token_ex
+     *
+     * @param string $extUserId 集成商系统中的用户ID
+     * @param int|null $merchantId 集成商系统中的商户ID,可选
+     * @param string|null $sessionId 集成商系统中的会话ID,可选
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {
+     *     "ext_user_id": "test",
+     *     "merchant_id": 1,
+     *     "session_id": "session123"
+     *   },
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function registerTokenEx($extUserId, $merchantId = 0, $sessionId = 'session123')
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [
+            'ext_user_id' => strval($extUserId),
+            'merchant_id' => $merchantId,
+            'session_id' => $sessionId
+        ];
+
+        return $this->postRequest('/register_token_ex', $time, $data);
+    }
+
+    // 调用API方法:create_demo_user
+    /**
+     * 调用API方法:create_demo_user
+     *
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {},
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function createDemoUser()
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [];
+
+        return $this->postRequest('/create_demo_user', $time, $data);
+    }
+
+    // 调用API方法:settings/games_list
+    /**
+     * 调用API方法:settings/games_list
+     *
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {},
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function getGamesList()
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [];
+
+        return $this->postRequest('/settings/games_list', $time, $data);
+    }
+
+    // 调用API方法:reports/game/ext_cashback_bets
+    /**
+     * 调用API方法:reports/game/ext_cashback_bets
+     *
+     * @param string $startDate UTC起始日期
+     * @param string $stopDate UTC结束日期
+     * @param int $gameType 游戏类型
+     * @param string|null $extUserId 可选,集成商系统中的玩家ID
+     * @return array 响应数据
+     *
+     * 调用示例:
+     * {
+     *   "time": "31-12-2018 10:35:58",
+     *   "data": {
+     *     "start_date": "2023-01-01",
+     *     "stop_date": "2023-01-31",
+     *     "game_type": 70,
+     *     "ext_user_id": "123"
+     *   },
+     *   "hash": "123456789987654321"
+     * }
+     */
+    public function getCashbackBets($startDate, $stopDate, $gameType, $extUserId = null)
+    {
+        $time = now()->format('d-m-Y H:i:s');
+        $data = [
+            'start_date' => $startDate,
+            'stop_date' => $stopDate,
+            'game_type' => $gameType,
+            'ext_user_id' => $extUserId
+        ];
+
+        return $this->postRequest('/reports/game/ext_cashback_bets', $time, $data);
+    }
+}
+

+ 241 - 0
app/Game/Services/AviatrixService.php

@@ -0,0 +1,241 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Util;
+use GuzzleHttp\Client;
+use Illuminate\Support\Facades\Crypt;
+
+class AviatrixService
+{
+    protected $client;
+    protected $baseUrl;
+    protected $secretKey;
+
+    protected $cid;
+
+    public function __construct()
+    {
+        $this->client = new Client();
+        $this->cid = '24680com';
+        $this->baseUrl = 'https://game-preprod.aviatrix.work';
+        $this->secretKey = '4fb4dbe2-0009-438c-8734-0e23a683588b';
+    }
+
+    protected function generateSignature($data)
+    {
+        return base64_encode(hash_hmac('md5', json_encode($data), $this->secretKey, true));
+    }
+
+    public function checkSignature($data,$signature)
+    {
+        //Util::WriteLog('24680_aviatri',$data);
+        return $this->generateSignature($data) == $signature;
+    }
+
+    protected function postRequest($endpoint, $data)
+    {
+        $signature = $this->generateSignature($data);
+
+        $response = $this->client->post($this->baseUrl . $endpoint, [
+            'headers' => [
+                'Content-Type' => 'application/json',
+                'X-Auth-Signature' => $signature,
+            ],
+            'json' => $data,
+        ]);
+
+        return json_decode($response->getBody(), true);
+    }
+
+
+    public function getPlayerInfo($data)
+    {
+        return $this->postRequest('/playerInfo', $data);
+    }
+
+    public function placeBet($data)
+    {
+        return $this->postRequest('/bet', $data);
+    }
+
+    public function processWin($data)
+    {
+        return $this->postRequest('/win', $data);
+    }
+
+    public function promoWin($data)
+    {
+        return $this->postRequest('/transactions/promoWin', $data);
+    }
+
+    public function healthCheck()
+    {
+        return ['healthy' => true];
+    }
+
+    public function closeMatch($data)
+    {
+        return $this->postRequest('/closeMatch', $data);
+    }
+
+    /**
+     * 获取游戏回合信息 (Game API)
+     * 请求参数:
+     * @param string  $cid , 品牌标识
+     * @param string  $playerId , 玩家标识
+     * @param string  $productId , 游戏标识
+     * @param string  $roundId , 回合标识 (betId和roundId至少提供一个)
+     * @param string  $betId , 下注标识 (betId和roundId至少提供一个)
+     *
+     * 调用示例:
+     * {
+     *   "cid": "somebrand",
+     *   "playerId": "john",
+     *   "productId": "nft-aviatrix",
+     *   "roundId": "2028207"
+     * }
+     */
+    public function getRoundInfo($cid, $playerId, $productId, $roundId = null, $betId = null)
+    {
+        $data = compact('cid', 'playerId', 'productId', 'roundId', 'betId');
+        return $this->postRequest('/game/round', $data);
+    }
+
+    /**
+     * 启动游戏 (Transfer Wallet API)
+     * 请求参数:
+     * @param string  $cid , 品牌标识
+     * @param string  $playerId , 玩家标识
+     * @param string  $productId , 游戏标识
+     * @param string  $currency , 货币类型
+     * @param string  $lang , 可选, 语言
+     * @param string  $lobbyUrl , 可选, 大厅URL
+     * @param boolean  $isDemo , 可选, 是否为演示模式
+     *
+     * 调用示例:
+     * {
+     *   "cid": "somebrand",
+     *   "playerId": "john",
+     *   "productId": "nft-aviatrix",
+     *   "currency": "USD",
+     *   "lang": "en",
+     *   "lobbyUrl": "https://example.com/lobby",
+     *   "isDemo": true
+     * }
+     */
+    public function launchGame($playerId, $lang = null, $lobbyUrl = null, $isDemo = null)
+    {
+        $cid = $this->cid;
+        $productId = 'nft-aviatrix';
+        $sessionToken = Crypt::encryptString($playerId);
+        $sessionToken = rtrim(strtr($sessionToken, '+/', '-_'), '=');
+        return $this->baseUrl.'?cid='.$cid.'&productId='.$productId.'&sessionToken='.$sessionToken.'&lang='.$lang;
+    }
+
+    /**
+     * 检查交易 (Transfer Wallet API)
+     * 请求参数:
+     * @param string  $cid , 品牌标识
+     * @param string  $txId , 事务标识
+     *
+     * 调用示例:
+     * {
+     *   "cid": "somebrand",
+     *   "txId": "4df40f77-2b38-43f4-b264-1d850f5a6715"
+     * }
+     */
+    public function checkTransaction($cid, $txId)
+    {
+        $data = compact('cid', 'txId');
+        return $this->postRequest('/transferwallet/checkTx', $data);
+    }
+
+    /**
+     * 获取玩家余额 (Transfer Wallet API)
+     * 请求参数:
+     * @param string  $cid , 品牌标识
+     * @param string  $playerId , 玩家标识
+     *
+     * 调用示例:
+     * {
+     *   "cid": "somebrand",
+     *   "playerId": "john"
+     * }
+     */
+    public function getBalance($cid, $playerId)
+    {
+        $data = compact('cid', 'playerId');
+        return $this->postRequest('/transferwallet/getBalance', $data);
+    }
+
+    /**
+     * 获取交易历史 (Transfer Wallet API)
+     * 请求参数:
+     * @param string  $cid , 品牌标识
+     * @param string  $playerId , 玩家标识
+     * @param integer  $limit , 可选, 返回记录的限制数
+     * @param string  $from , 可选, 起始时间
+     *
+     * 调用示例:
+     * {
+     *   "cid": "somebrand",
+     *   "playerId": "john",
+     *   "limit": 10,
+     *   "from": "2023-01-01"
+     * }
+     */
+    public function getTransactionHistory($cid, $playerId, $limit = null, $from = null)
+    {
+        $data = compact('cid', 'playerId', 'limit', 'from');
+        return $this->postRequest('/transferwallet/getHistory', $data);
+    }
+
+    /**
+     * 存款 (Transfer Wallet API)
+     * 请求参数:
+     * @param string  $cid , 品牌标识
+     * @param string  $playerId , 玩家标识
+     * @param string  $txId , 事务标识
+     * @param string  $currency , 货币类型
+     * @param integer  $amount , 存款金额
+     *
+     * 调用示例:
+     * {
+     *   "cid": "somebrand",
+     *   "playerId": "john",
+     *   "txId": "4df40f77-2b38-43f4-b264-1d850f5a6715",
+     *   "currency": "EUR",
+     *   "amount": 1234
+     * }
+     */
+    public function deposit($cid, $playerId, $txId, $currency, $amount)
+    {
+        $data = compact('cid', 'playerId', 'txId', 'currency', 'amount');
+        return $this->postRequest('/transferwallet/deposit', $data);
+    }
+
+    /**
+     * 取款 (Transfer Wallet API)
+     * 请求参数:
+     * @param string  $cid , 品牌标识
+     * @param string  $playerId , 玩家标识
+     * @param string  $txId , 事务标识
+     * @param string  $currency , 货币类型
+     * @param integer  $amount , 取款金额
+     *
+     * 调用示例:
+     * {
+     *   "cid": "somebrand",
+     *   "playerId": "john",
+     *   "txId": "4df40f77-2b38-43f4-b264-1d850f5a6715",
+     *   "currency": "EUR",
+     *   "amount": 1234
+     * }
+     */
+    public function withdraw($cid, $playerId, $txId, $currency, $amount)
+    {
+        $data = compact('cid', 'playerId', 'txId', 'currency', 'amount');
+        return $this->postRequest('/transferwallet/withdraw', $data);
+    }
+}

+ 439 - 0
app/Game/Services/BetbyService.php

@@ -0,0 +1,439 @@
+<?php
+
+namespace App\Game\Services;
+
+
+use App\Game\BetBy\PlayerDataItem;
+use App\Game\GlobalUserInfo;
+use App\Notification\TelegramBot;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
+use App\Game\BetBy\PlayerDetails;
+use App\Game\BetBy\PlayerSegment;
+use App\Game\BetBy\BonusTemplate;
+use App\Game\BetBy\TemplateItem;
+use App\Game\BetBy\BonusItem;
+use App\Game\BetBy\Bonus;
+use App\Game\BetBy\ErrorItem;
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+use Exception;
+use Illuminate\Support\Facades\Log;
+
+class BetbyService
+{
+    protected $client;
+    protected $baseUrl;
+    public $operatorId;
+    public $brandId;
+    protected $privateKey;
+    protected $publicKey;
+
+    //-----BEGIN PUBLIC KEY-----MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENPcid+kKnpbbW3HuY+m1uRbishkC
+    //nhD1k1apZyZUqLy3kmCtBTdKfNaOrk9hjOmJjD84hRBHjwLCgPraOATGAw==-----END PUBLIC KEY-----
+    public function __construct()
+    {
+        $this->client = new Client();
+
+        $this->baseUrl = "https://external-api.invisiblesport.com/api/v1/external_api/";
+        $this->operatorId = '2431055242410987528';
+        $this->brandId = '2431056314022113280';
+        $this->privateKey = '-----BEGIN EC PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgyHuItL+sCjpUTByK
+A3jNkUlPIlGOcHBArKC72/6TvHKhRANCAARojqkn6dbPyuIEtT0MeI4hUUZMxabx
+2PKTkGp4jxNcjMsCFMQLMRNg5LMXBHyT+IIRlSw2EyQa7zj6RamDu6q6
+-----END EC PRIVATE KEY-----';
+        $this->publicKey = '-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqjOE0GOdf7wQrMcoy3oX
+7AGibUvcDUif58w9J2PDLto1/4+Cp//H7uwSAHQy6ypENvbvx+4U4KS7YcIBB8ry
+p8VrSFlQ1UovWmDP+SAP3vf+FyafV93eBeFIjyq9B3ADWVAHNoR2EuRJT429T46k
+SZCjYzZCA6QH1mB98XuHjjXBMA5AmJJrHJn8tIVFx//0bpIgijM55iwGC8MzYt3c
+74gmk8U6a88KwSYQA3BafsQetmZeh/YbHJ2vmCzkZqpsRjsejsPYud1cEtt1qFL0
+MCXCZ17zKuXidktTpUsYa0qLP1X59TzDTLR3txccq1TfyEFfKYpjmTq3H7HPc5r4
+K/J0xAsPJLU8r+IysyE3hiO+jheF1JTQKtZnbO6Ul73xZxUN8nqkOFkwTxqiDkIm
+jpqV81rbVbZ85OeNuCjdwNCkdaq+jaSNubjW7+vCZUBG7YkzhB2F+AwnbWmLyNuw
+GAktH88odLMUIfHr22BQZNTGlAcoDeTps0ZGbHZxMT05yYmGvh1inQlspAmkIY5Q
+my6QqaNarnxVYRldiGknJwcwNV2ZpuFn6EQgVIIrdsqfQhsb+zbfrrpVV2Cfbu53
+kjUXVyyl5zLa3ijDf4X/ujTIFCH0yvJ6DohMC5TVM6WaunYFVpSrhk5Naiq+dJJF
+PpwSndlHML59u+kKz6VtRqsCAwEAAQ==
+-----END PUBLIC KEY-----';
+    }
+
+    /**
+     * 验证并解码JWT payload
+     *
+     * @param string $token JWT token
+     * @return array 解码后的数据
+     */
+    public function decodePayload(string $token): array
+    {
+        return (array) JWT::decode($token, new Key($this->publicKey, 'RS256'));
+    }
+
+    /**
+     * 生成JWT payload
+     *
+     * @param array $data 数据
+     * @return string 生成的JWT token
+     */
+    public function encodePayload(array $data): string
+    {
+        $now = time();
+        $payload = [
+            'iat' => $now,
+            'exp' => $now + 3600,
+            'jti' => strval(rand()),
+            'iss' => $this->brandId,
+            'aud' => $this->brandId,
+            'nbf' => $now,
+            'payload' => $data
+        ];
+        return JWT::encode($payload, $this->privateKey, 'ES256');
+    }
+    public function getDefaultJWT($user)
+    {
+
+        if(RouteService::isTestSite()) {
+            return (new BetbyTestService())->getDefaultJWT($user);
+        }
+        $user=(array) $user;
+        $now = time();
+        $payload = [
+            'iss' => $this->brandId,
+            'sub' => $user['GlobalUID'],
+            'name' => $user['NickName'],
+            'iat' => $now,
+            'exp' => $now + 3600,
+            'jti' => strval(rand()),
+            'lang'=>$user['DefaultLanguage']??GlobalUserInfo::getLocale(),
+            'currency'=>env('CONFIG_24680_CURRENCY')
+        ];
+        return JWT::encode($payload, $this->privateKey, 'ES256');
+    }
+
+    /**
+     * 处理请求异常
+     *
+     * @param RequestException $e 异常对象
+     * @return array 错误信息
+     */
+    private function handleRequestException(RequestException $e): array
+    {
+        $response = $e->getResponse();
+        if ($response) {
+            $body = json_decode($response->getBody(), true);
+            if (isset($body['error'])) {
+
+                (new TelegramBot())->sendMsgWithEnv($body);
+                return [
+                    'error' => new ErrorItem($body['error'])
+                ];
+            }
+        }
+        return [
+            'error' => new ErrorItem(['name' => 'UnknownError', 'description' => 'An unknown error occurred.'])
+        ];
+    }
+
+    /**
+     * 处理响应数据
+     *
+     * @param $response
+     * @return mixed
+     */
+    private function handleResponse($response)
+    {
+        $data = json_decode($response->getBody(), true);
+        $data= $this->decodePayload($data);
+        if (isset($data['error'])) {
+            (new TelegramBot())->sendMsgWithEnv($data);
+            return ['error' => new ErrorItem($data['error'])];
+        }
+        return $data;
+    }
+
+    public function ping()
+    {
+        try {
+            $response = $this->client->get($this->baseUrl . '/ping' );
+            $data = json_decode($response->getBody(),true);
+            return $data;
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+    /**
+     * 调用Betby的PLAYER_DETAILS接口
+     * 获取玩家详细信息
+     *
+     * @param string $externalPlayerId 玩家ID
+     * @param string $segment 分段
+     * @param array $depositMethods 存款方式
+     * @return PlayerDetails|array
+     */
+    public function getPlayerDetails(string $externalPlayerId, string $segment, array $depositMethods)
+    {
+        $payload = $this->encodePayload([
+            'external_player_id' => $externalPlayerId,
+            'segment' => $segment,
+            'deposit_methods' => $depositMethods
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/player_details', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return new PlayerDetails($data);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的PLAYER_SEGMENT接口
+     * 获取玩家分段信息
+     *
+     * @param string $externalPlayerId 玩家ID
+     * @return PlayerSegment|array
+     */
+    public function getPlayerSegment(string $externalPlayerId)
+    {
+        $payload = $this->encodePayload([
+            'brand_id' => $this->brandId,
+            'external_player_id' => $externalPlayerId
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/player/segment', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return new PlayerSegment($data);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的TEMPLATES接口
+     * 获取所有奖金模板
+     *
+     * @return array<TemplateItem>|array
+     */
+    public function getBonusTemplates()
+    {
+        $payload = $this->encodePayload(['operator_id' => $this->operatorId]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus/templates', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return array_map(function($template) {
+                return new TemplateItem($template);
+            }, $data['items']);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的TEMPLATE接口
+     * 获取指定ID的奖金模板
+     *
+     * @param string $templateId 奖金模板ID
+     * @return TemplateItem|array
+     */
+    public function getBonusTemplate(string $templateId)
+    {
+        $payload = $this->encodePayload([
+            'operator_id' => $this->operatorId,
+            'template_id' => $templateId
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus/template', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return new TemplateItem($data);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的PLAYER_BONUSES接口
+     * 获取玩家的所有奖金
+     *
+     * @param string $externalPlayerId 玩家ID
+     * @return array<BonusItem>|array
+     */
+    public function getPlayerBonuses(string $externalPlayerId)
+    {
+        $payload = $this->encodePayload([
+            'brand_id' => $this->brandId,
+            'external_player_id' => $externalPlayerId
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus/player_bonuses', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return array_map(function($bonus) {
+                return new BonusItem($bonus);
+            }, $data['items']);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的BONUS接口
+     * NB. Because of technical features one request can contain around 1000 players.
+     * Please, split your list of players in case there are significantly more than 1000 players.
+     * 获取指定ID的奖金信息
+     *
+     * @param string $bonusId 奖金ID
+     * @return BonusItem|array
+     */
+    public function getBonus(string $bonusId)
+    {
+        $payload = $this->encodePayload([
+            'brand_id' => $this->brandId,
+            'bonus_id' => $bonusId
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return new BonusItem($data);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的MASS_GIVE_BONUS接口
+     * 批量授予奖金
+     *
+     * @param string $brandId 品牌ID
+     * @param string $templateId 奖金模板ID
+     * @param PlayerDataItem[] $playersData 玩家数据
+     * @return array 响应数据
+     */
+    public function massGiveBonus(string $brandId, string $templateId, array $playersData): array
+    {
+        $payload = $this->encodePayload([
+            'brand_id' => $brandId,
+            'template_id' => $templateId,
+            'players_data' => array_map(function($playerData) {
+                return [
+                    'external_player_id' => $playerData->externalPlayerId,
+                    'currency' => $playerData->currency,
+                    'amount' => $playerData->amount,
+                    'force_activated' => $playerData->forceActivated,
+                ];
+            }, $playersData)
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus/mass_give_bonus', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data=$this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            $bonusItems = [];
+            foreach (  $data['result'] as $BonusPlayerItem){
+                foreach ($BonusPlayerItem as $playerID => $bonus) {
+                    if(isset($bonus['error'])){
+                        $bonusItems[$playerID]= ['error'=>new ErrorItem($bonus['error'])];
+                    }else{
+                        $bonusItems[$playerID] = new BonusItem($bonus);
+                    }
+                }
+            }
+            return $bonusItems;
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的REVOKE_BONUS接口
+     * 撤销指定ID的奖金
+     *
+     * @param string[] $bonuses_ids 奖金IDs
+     * @return array<BonusItem>|array
+     */
+    public function revokeBonus(array $bonuses_ids)
+    {
+        $payload = $this->encodePayload([
+            'brand_id' => $this->brandId,
+            'bonus_ids' => $bonuses_ids
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus/revoke', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data=$this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            $bonusItems = [];
+            foreach (  $data['result'] as $BonusPlayerItem){
+                foreach ($BonusPlayerItem as $playerID => $bonus) {
+                    if(isset($bonus['error'])){
+                        $bonusItems[$playerID]= ['error'=>new ErrorItem($bonus['error'])];
+                    }else{
+                        $bonusItems[$playerID] = new BonusItem($bonus);
+                    }
+                }
+            }
+            return $bonusItems;
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+}

+ 434 - 0
app/Game/Services/BetbyTestService.php

@@ -0,0 +1,434 @@
+<?php
+
+namespace App\Game\Services;
+
+
+use App\Game\BetBy\PlayerDataItem;
+use App\Game\GlobalUserInfo;
+use App\Notification\TelegramBot;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
+use App\Game\BetBy\PlayerDetails;
+use App\Game\BetBy\PlayerSegment;
+use App\Game\BetBy\BonusTemplate;
+use App\Game\BetBy\TemplateItem;
+use App\Game\BetBy\BonusItem;
+use App\Game\BetBy\Bonus;
+use App\Game\BetBy\ErrorItem;
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+use Exception;
+use Illuminate\Support\Facades\Log;
+
+class BetbyTestService
+{
+    protected $client;
+    protected $baseUrl;
+    public $operatorId;
+    public $brandId;
+    protected $privateKey;
+    protected $publicKey;
+
+    //-----BEGIN PUBLIC KEY-----MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENPcid+kKnpbbW3HuY+m1uRbishkC
+    //nhD1k1apZyZUqLy3kmCtBTdKfNaOrk9hjOmJjD84hRBHjwLCgPraOATGAw==-----END PUBLIC KEY-----
+    public function __construct()
+    {
+        $this->client = new Client();
+
+        $this->baseUrl = "https://external-api.invisiblesport.com/api/v1/external_api";
+        $this->operatorId = '2424946715804176387';
+        $this->brandId = '2424948766365847552';
+        $this->privateKey = '-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIE+5vrcbCZ+7UyavHgEmaNDMVc4V/OmQcK8hMnoaBI0toAoGCCqGSM49
+AwEHoUQDQgAENPcid+kKnpbbW3HuY+m1uRbishkCnhD1k1apZyZUqLy3kmCtBTdKfNaOrk9hjOmJjD84hRBHjwLCgPraOATGAw==
+-----END EC PRIVATE KEY-----';
+        $this->publicKey = '-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjmWwsejCzJHljSDpYtwP
+uA5HYIP49dNaIuY4ambFiYSTLCPEMqq6kJ9Xo6nphW0mfV+VsDOqcynkzgNAG2y0
+kuEe7TF0Tpc5OgTrj9PEm6kxCryxnHSRB56Wv22RQlpqYEO/wQiTXxecgceVL/Dm
+vQ91peCcuK6GtSr46RzT/lzsx6DrcGTdUb0aJz7sszmhN6YG8RAb/vHGuekfajZi
+UilPU51NOFqRLtSYTxBG3uakPhNtwnxjTFoQZmQ3jPFXCXwn+FnocLIPK5UPJe35
+sbnqGhWyIPnTPf75HE6WqdP4GnMrvO1oIaIluuyM1WjCxTVVqHUnUJpMOAPK4QtK
+CkU8kWV3URJrTyuQ9A8dODcCvRjZZp96Dvd6taJHN2/brZ78zX3gBP4C8MrtP1na
+lzs/Dr7Uo5yQoe/CW5cHczRG/XtbOhloqCJXu56vbMC9DQIDVMEythprAe6haVPE
+GBJ2Ci3VgIMY91HILsE42Aoj5uN2LiHLWyh5UN/ESJeY/BL8Vxkv6VDaPjRZD7ee
+JeepqOs7zNo2srXBfJIQ3n0ZZULIO/hdZInTLXnAGaP+5eUjkSqsQWt3OttRXmA7
+7Ps7k45ghFB1ZFa+t00omOpBzP7HF6f8sdnFNSugvc2vW8c3+Noj74PimX9AcJfd
++rFDv70YSd67t+E/9DSo4l8CAwEAAQ==
+-----END PUBLIC KEY-----';
+    }
+
+    /**
+     * 验证并解码JWT payload
+     *
+     * @param string $token JWT token
+     * @return array 解码后的数据
+     */
+    public function decodePayload(string $token): array
+    {
+        return (array) JWT::decode($token, new Key($this->publicKey, 'RS256'));
+    }
+
+    /**
+     * 生成JWT payload
+     *
+     * @param array $data 数据
+     * @return string 生成的JWT token
+     */
+    public function encodePayload(array $data): string
+    {
+        $now = time();
+        $payload = [
+            'iat' => $now,
+            'exp' => $now + 3600,
+            'jti' => strval(rand()),
+            'iss' => $this->brandId,
+            'aud' => $this->brandId,
+            'nbf' => $now,
+            'payload' => $data
+        ];
+        return JWT::encode($payload, $this->privateKey, 'ES256');
+    }
+    public function getDefaultJWT($user)
+    {
+        $user=(array) $user;
+        $now = time();
+        $payload = [
+            'iss' => $this->brandId,
+            'sub' => $user['GlobalUID'],
+            'name' => $user['NickName'],
+            'iat' => $now,
+            'exp' => $now + 3600,
+            'jti' => strval(rand()),
+            'lang'=>$user['DefaultLanguage']??GlobalUserInfo::getLocale(),
+            'currency'=>env('CONFIG_24680_CURRENCY')
+        ];
+        return JWT::encode($payload, $this->privateKey, 'ES256');
+    }
+
+    /**
+     * 处理请求异常
+     *
+     * @param RequestException $e 异常对象
+     * @return array 错误信息
+     */
+    private function handleRequestException(RequestException $e): array
+    {
+        $response = $e->getResponse();
+        if ($response) {
+            $body = json_decode($response->getBody(), true);
+            if (isset($body['error'])) {
+
+                (new TelegramBot())->sendMsgWithEnv($body);
+                return [
+                    'error' => new ErrorItem($body['error'])
+                ];
+            }
+        }
+        return [
+            'error' => new ErrorItem(['name' => 'UnknownError', 'description' => 'An unknown error occurred.'])
+        ];
+    }
+
+    /**
+     * 处理响应数据
+     *
+     * @param $response
+     * @return mixed
+     */
+    private function handleResponse($response)
+    {
+        $data = json_decode($response->getBody(), true);
+        $data= $this->decodePayload($data);
+        if (isset($data['error'])) {
+            (new TelegramBot())->sendMsgWithEnv($data);
+            return ['error' => new ErrorItem($data['error'])];
+        }
+        return $data;
+    }
+
+    public function ping()
+    {
+        try {
+            $response = $this->client->get($this->baseUrl . '/ping' );
+            $data = json_decode($response->getBody(),true);
+            return $data;
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+    /**
+     * 调用Betby的PLAYER_DETAILS接口
+     * 获取玩家详细信息
+     *
+     * @param string $externalPlayerId 玩家ID
+     * @param string $segment 分段
+     * @param array $depositMethods 存款方式
+     * @return PlayerDetails|array
+     */
+    public function getPlayerDetails(string $externalPlayerId, string $segment, array $depositMethods)
+    {
+        $payload = $this->encodePayload([
+            'external_player_id' => $externalPlayerId,
+            'segment' => $segment,
+            'deposit_methods' => $depositMethods
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/player_details', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return new PlayerDetails($data);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的PLAYER_SEGMENT接口
+     * 获取玩家分段信息
+     *
+     * @param string $externalPlayerId 玩家ID
+     * @return PlayerSegment|array
+     */
+    public function getPlayerSegment(string $externalPlayerId)
+    {
+        $payload = $this->encodePayload([
+            'brand_id' => $this->brandId,
+            'external_player_id' => $externalPlayerId
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/player/segment', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return new PlayerSegment($data);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的TEMPLATES接口
+     * 获取所有奖金模板
+     *
+     * @return array<TemplateItem>|array
+     */
+    public function getBonusTemplates()
+    {
+        $payload = $this->encodePayload(['operator_id' => $this->operatorId]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus/templates', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return array_map(function($template) {
+                return new TemplateItem($template);
+            }, $data['items']);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的TEMPLATE接口
+     * 获取指定ID的奖金模板
+     *
+     * @param string $templateId 奖金模板ID
+     * @return TemplateItem|array
+     */
+    public function getBonusTemplate(string $templateId)
+    {
+        $payload = $this->encodePayload([
+            'operator_id' => $this->operatorId,
+            'template_id' => $templateId
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus/template', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return new TemplateItem($data);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的PLAYER_BONUSES接口
+     * 获取玩家的所有奖金
+     *
+     * @param string $externalPlayerId 玩家ID
+     * @return array<BonusItem>|array
+     */
+    public function getPlayerBonuses(string $externalPlayerId)
+    {
+        $payload = $this->encodePayload([
+            'brand_id' => $this->brandId,
+            'external_player_id' => $externalPlayerId
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus/player_bonuses', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return array_map(function($bonus) {
+                return new BonusItem($bonus);
+            }, $data['items']);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的BONUS接口
+     * NB. Because of technical features one request can contain around 1000 players.
+     * Please, split your list of players in case there are significantly more than 1000 players.
+     * 获取指定ID的奖金信息
+     *
+     * @param string $bonusId 奖金ID
+     * @return BonusItem|array
+     */
+    public function getBonus(string $bonusId)
+    {
+        $payload = $this->encodePayload([
+            'brand_id' => $this->brandId,
+            'bonus_id' => $bonusId
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data = $this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            return new BonusItem($data);
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的MASS_GIVE_BONUS接口
+     * 批量授予奖金
+     *
+     * @param string $brandId 品牌ID
+     * @param string $templateId 奖金模板ID
+     * @param PlayerDataItem[] $playersData 玩家数据
+     * @return array 响应数据
+     */
+    public function massGiveBonus(string $brandId, string $templateId, array $playersData): array
+    {
+        $payload = $this->encodePayload([
+            'brand_id' => $brandId,
+            'template_id' => $templateId,
+            'players_data' => array_map(function($playerData) {
+                return [
+                    'external_player_id' => $playerData->externalPlayerId,
+                    'currency' => $playerData->currency,
+                    'amount' => $playerData->amount,
+                    'force_activated' => $playerData->forceActivated,
+                ];
+            }, $playersData)
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus/mass_give_bonus', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data=$this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            $bonusItems = [];
+            foreach (  $data['result'] as $BonusPlayerItem){
+                foreach ($BonusPlayerItem as $playerID => $bonus) {
+                    if(isset($bonus['error'])){
+                        $bonusItems[$playerID]= ['error'=>new ErrorItem($bonus['error'])];
+                    }else{
+                        $bonusItems[$playerID] = new BonusItem($bonus);
+                    }
+                }
+            }
+            return $bonusItems;
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 调用Betby的REVOKE_BONUS接口
+     * 撤销指定ID的奖金
+     *
+     * @param string[] $bonuses_ids 奖金IDs
+     * @return array<BonusItem>|array
+     */
+    public function revokeBonus(array $bonuses_ids)
+    {
+        $payload = $this->encodePayload([
+            'brand_id' => $this->brandId,
+            'bonus_ids' => $bonuses_ids
+        ]);
+
+        try {
+            $response = $this->client->post($this->baseUrl . '/bonus/revoke', [
+                'headers' => ['Content-Type' => 'application/json'],
+                'json' => ['payload' => $payload]
+            ]);
+
+            $data=$this->handleResponse($response);
+            if (isset($data['error'])) {
+                return $data;
+            }
+            $bonusItems = [];
+            foreach (  $data['result'] as $BonusPlayerItem){
+                foreach ($BonusPlayerItem as $playerID => $bonus) {
+                    if(isset($bonus['error'])){
+                        $bonusItems[$playerID]= ['error'=>new ErrorItem($bonus['error'])];
+                    }else{
+                        $bonusItems[$playerID] = new BonusItem($bonus);
+                    }
+                }
+            }
+            return $bonusItems;
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+}

+ 30 - 0
app/Game/Services/CheckGameLogin.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Http\Controllers\Game\LoginController;
+use Closure;
+
+class CheckGameLogin
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+
+        $user = LoginController::checkLogin($request);
+        if ($user) {
+            $request->globalUser = $user;
+            $request->setUserResolver(function () use ($user) {
+                return $user;
+            });
+        }
+
+        return $next($request);
+    }
+}

+ 594 - 0
app/Game/Services/EvoplayService.php

@@ -0,0 +1,594 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Game\GlobalUserInfo;
+use App\Http\helper\NumConfig;
+use App\Models\AccountsInfo;
+use App\Notification\TelegramBot;
+use App\Util;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+
+class EvoplayService
+{
+    protected $client;
+    protected $currency;
+    protected $apiUrl;
+    protected $projectId;
+    protected $secretKey;
+    protected $version;
+    protected $systemId;
+
+    public function __construct()
+    {
+        $this->client = new Client();
+        $this->currency = env('CONFIG_24680_CURRENCY');
+        $this->apiUrl = env('EVOPLAY_API_URL', 'https://api.evoplay.games');
+        $this->projectId = env('EVOPLAY_PROJECT_ID', '11800');
+        $this->secretKey = env('EVOPLAY_SECRET_KEY', '68e071e88a198e25d847c30d0724eb2c');
+        $this->version = env('EVOPLAY_API_VERSION', '1');
+        $this->systemId = env('EVOPLAY_SYSTEM_ID', '24680-com-test');
+    }
+
+    /**
+     * 调用子API
+     *
+     * @param string $username
+     * @param \Illuminate\Http\Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function callSubApi($username, $request)
+    {
+        Util::WriteLog('evoplay', 'callsubapi');
+        $apiurl = ServerService::GetApiByGUID($username);
+
+        try {
+            $postData = $request->post();
+
+            $response = $this->client->post($apiurl . $_SERVER['REQUEST_URI'], [
+                'verify' => false,
+                'form_params' => $postData,
+            ]);
+            $res = json_decode($response->getBody(), true);
+            Util::WriteLog('evoplay', $res);
+            return $res;
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    /**
+     * 处理请求异常
+     *
+     * @param \Exception $e
+     * @return array
+     */
+    private function handleRequestException(\Exception $e)
+    {
+        Util::WriteLog('evoplay_error', $e->getMessage());
+        return [
+            'status' => 'error',
+            'error' => [
+                'code' => 'INTERNAL_ERROR',
+                'message' => 'Internal server error'
+            ]
+        ];
+    }
+
+    /**
+     * 生成签名
+     *
+     * @param array $data 请求数据
+     * @return string 签名
+     */
+    public function generateSignature($data)
+    {
+        // 按字母顺序排序参数
+        ksort($data);
+
+        // 将参数转换为查询字符串
+        $query = http_build_query($data);
+
+        // 生成签名
+        return hash_hmac('sha256', $query, $this->secretKey);
+    }
+
+    /**
+     * 获取游戏列表
+     *
+     * @return array
+     */
+    public function getGamesList()
+    {
+        try {
+            $data = [
+                'project' => $this->projectId,
+                'version' => $this->version,
+                'need_extra_data' => 1,
+            ];
+
+            // 生成签名
+            $data['signature'] = $this->generateSignature($data);
+
+            // 调用游戏列表API
+            $response = $this->client->post($this->apiUrl . '/Game/getList', [
+                'verify' => false,
+                'form_params' => $data
+            ]);
+
+            return json_decode($response->getBody(), true);
+        } catch (\Exception $e) {
+            Util::WriteLog('evoplay_error', $e->getMessage());
+            return ['error' => $e->getMessage()];
+        }
+    }
+
+    /**
+     * 获取游戏启动URL
+     *
+     * @param string $gameId 游戏ID
+     * @param string $userId 用户ID
+     * @param string $language 语言
+     * @param string $currency 货币
+     * @param bool $demo 是否为试玩模式
+     * @return string|null
+     */
+    public function getGameUrl($gameId, $userId, $language = 'en', $currency = null, $demo = false)
+    {
+        try {
+            if (!$currency) {
+                $currency = $this->currency;
+            }
+
+            $data = [
+                'project_id' => $this->projectId,
+                'version' => $this->version,
+                'system_id' => $this->systemId,
+                'user_id' => $userId,
+                'game_id' => $gameId,
+                'currency' => $currency,
+                'language' => $language,
+                'denomination' => 1,
+                'return_url' => url('/'),
+                'callback_url' => url('/api/callback/evoplay'),
+                'callback_version' => 2,
+                'signature' => ''
+            ];
+
+            if ($demo) {
+                $data['demo'] = 1;
+            }
+
+            // 生成签名
+            $data['signature'] = $this->generateSignature($data);
+
+            // 调用游戏启动API
+            $response = $this->client->post($this->apiUrl . '/game/geturl', [
+                'verify' => false,
+                'form_params' => $data
+            ]);
+
+            $result = json_decode($response->getBody(), true);
+
+            if (isset($result['status']) && $result['status'] === 'ok' && isset($result['url'])) {
+                return $result['url'];
+            }
+
+            Util::WriteLog('evoplay_error', 'Failed to get game URL: ' . json_encode($result));
+            return null;
+        } catch (\Exception $e) {
+            Util::WriteLog('evoplay_error', $e->getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 玩家验证
+     *
+     * @param string $userId 用户ID
+     * @return array 用户信息
+     */
+    public function validateUser($userId)
+    {
+        try {
+            // 获取用户信息
+            $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $userId);
+            if (!$user) {
+                return [
+                    'status' => 'error',
+                    'error' => [
+                        'code' => 'USER_NOT_FOUND',
+                        'message' => 'User not found'
+                    ]
+                ];
+            }
+
+            // 获取用户余额
+            $balance = GlobalUserInfo::getScoreByUserID($user->UserID);
+
+            return [
+                'status' => 'ok',
+                'data' => [
+                    'id' => $userId,
+                    'name' => $user->NickName,
+                    'balance' => $balance,
+                    'currency' => $this->currency
+                ]
+            ];
+        } catch (\Exception $e) {
+            Util::WriteLog('evoplay_error', $e->getMessage());
+            return [
+                'status' => 'error',
+                'error' => [
+                    'code' => 'INTERNAL_ERROR',
+                    'message' => 'Internal server error'
+                ]
+            ];
+        }
+    }
+
+    /**
+     * 处理余额查询
+     *
+     * @param string $userId 用户ID
+     * @return array 余额信息
+     */
+    public function getBalance($userId)
+    {
+        try {
+            // 获取用户信息
+            $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $userId);
+            if (!$user) {
+                return [
+                    'status' => 'error',
+                    'error' => [
+                        'code' => 'USER_NOT_FOUND',
+                        'message' => 'User not found'
+                    ]
+                ];
+            }
+
+            // 获取用户余额
+            $balance = GlobalUserInfo::getScoreByUserID($user->UserID);
+
+            return [
+                'status' => 'ok',
+                'data' => [
+                    'balance' => $balance,
+                    'currency' => $this->currency
+                ]
+            ];
+        } catch (\Exception $e) {
+            Util::WriteLog('evoplay_error', $e->getMessage());
+            return [
+                'status' => 'error',
+                'error' => [
+                    'code' => 'INTERNAL_ERROR',
+                    'message' => 'Internal server error'
+                ]
+            ];
+        }
+    }
+
+    /**
+     * 处理下注请求
+     *
+     * @param string $userId 用户ID
+     * @param string $gameId 游戏ID
+     * @param float $amount 金额
+     * @param string $transactionId 交易ID
+     * @param string $roundId 回合ID
+     * @return array 处理结果
+     */
+    public function bet($userId, $gameId, $amount, $transactionId, $roundId)
+    {
+        try {
+            // 获取用户信息
+            $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $userId);
+            if (!$user) {
+                return [
+                    'status' => 'error',
+                    'error' => [
+                        'code' => 'USER_NOT_FOUND',
+                        'message' => 'User not found'
+                    ]
+                ];
+            }
+
+            $userId = $user->UserID;
+
+            // 检查交易是否已处理
+            $transactionKey = 'evoplay_tx_' . $transactionId;
+            if (Redis::exists($transactionKey)) {
+                // 交易已处理,返回上次的结果
+                $previousResult = json_decode(Redis::get($transactionKey), true);
+                return $previousResult;
+            }
+
+            // 获取当前余额
+            $currentBalance = GlobalUserInfo::getScoreByUserID($userId) * NumConfig::NUM_VALUE;
+            $amountInCents = intval($amount * NumConfig::NUM_VALUE);
+
+            // 检查余额是否足够
+            if ($currentBalance < $amountInCents) {
+                $errorResponse = [
+                    'status' => 'error',
+                    'error' => [
+                        'code' => 'INSUFFICIENT_FUNDS',
+                        'message' => 'Insufficient funds'
+                    ]
+                ];
+                return $errorResponse;
+            }
+
+            // 获取独占锁确保事务安全
+            $res = SetNXLock::getExclusiveLock('evoplay_bet_' . $transactionId, 86400);
+            if (!$res) {
+                // 已经处理过,返回之前的结果
+                return $this->getBalance($userId);
+            }
+
+            // 扣除余额
+            DB::connection('write')->table('QPTreasureDB.dbo.GameScoreInfo')
+                ->where('UserID', $userId)
+                ->decrement('Score', $amountInCents);
+
+            // 记录下注
+            PlatformService::platformBet('evoplay', $gameId, $amountInCents, $userId);
+
+            // 保存交易数据用于可能的取消
+            Redis::set('evoplay_bet_' . $transactionId, $gameId . '_' . $amountInCents . '_' . $roundId);
+            Redis::expire('evoplay_bet_' . $transactionId, 86400);
+
+            // 更新当前余额
+            $currentBalance -= $amountInCents;
+
+            // 构造成功响应
+            $response = [
+                'status' => 'ok',
+                'data' => [
+                    'balance' => $currentBalance / NumConfig::NUM_VALUE,
+                    'currency' => $this->currency,
+                    'transaction_id' => $transactionId
+                ]
+            ];
+
+            // 缓存交易结果
+            Redis::set($transactionKey, json_encode($response));
+            Redis::expire($transactionKey, 86400);
+
+            return $response;
+        } catch (\Exception $e) {
+            Util::WriteLog('evoplay_error', $e->getMessage());
+            return [
+                'status' => 'error',
+                'error' => [
+                    'code' => 'INTERNAL_ERROR',
+                    'message' => 'Internal server error'
+                ]
+            ];
+        }
+    }
+
+    /**
+     * 处理赢钱请求
+     *
+     * @param string $userId 用户ID
+     * @param string $gameId 游戏ID
+     * @param float $amount 金额
+     * @param string $transactionId 交易ID
+     * @param string $roundId 回合ID
+     * @param string $betTransactionId 下注交易ID
+     * @return array 处理结果
+     */
+    public function win($userId, $gameId, $amount, $transactionId, $roundId, $betTransactionId = null)
+    {
+        try {
+            // 获取用户信息
+            $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $userId);
+            if (!$user) {
+                return [
+                    'status' => 'error',
+                    'error' => [
+                        'code' => 'USER_NOT_FOUND',
+                        'message' => 'User not found'
+                    ]
+                ];
+            }
+
+            $userId = $user->UserID;
+
+            // 检查交易是否已处理
+            $transactionKey = 'evoplay_tx_' . $transactionId;
+            if (Redis::exists($transactionKey)) {
+                // 交易已处理,返回上次的结果
+                $previousResult = json_decode(Redis::get($transactionKey), true);
+                return $previousResult;
+            }
+
+            // 获取当前余额
+            $currentBalance = GlobalUserInfo::getScoreByUserID($userId) * NumConfig::NUM_VALUE;
+            $amountInCents = intval($amount * NumConfig::NUM_VALUE);
+
+            // 获取独占锁确保事务安全
+            $res = SetNXLock::getExclusiveLock('evoplay_win_' . $transactionId, 86400);
+            if (!$res) {
+                // 已经处理过,返回之前的结果
+                return $this->getBalance($userId);
+            }
+
+            // 检查投注记录
+            $originalBet = 0;
+            if ($betTransactionId) {
+                $betKey = 'evoplay_bet_' . $betTransactionId;
+                if (Redis::exists($betKey)) {
+                    $betInfo = Redis::get($betKey);
+                    $parts = explode('_', $betInfo);
+                    $originalBet = isset($parts[1]) ? intval($parts[1]) : 0;
+                }
+            }
+
+            // 增加余额
+            DB::connection('write')->table('QPTreasureDB.dbo.GameScoreInfo')
+                ->where('UserID', $userId)
+                ->increment('Score', $amountInCents);
+
+            // 记录赢取
+            PlatformService::platformWin(
+                $userId,
+                'evoplay',
+                $gameId,
+                $amountInCents,
+                $originalBet,
+                $currentBalance + $amountInCents
+            );
+
+            // 更新当前余额
+            $currentBalance += $amountInCents;
+
+            // 构造成功响应
+            $response = [
+                'status' => 'ok',
+                'data' => [
+                    'balance' => $currentBalance / NumConfig::NUM_VALUE,
+                    'currency' => $this->currency,
+                    'transaction_id' => $transactionId
+                ]
+            ];
+
+            // 缓存交易结果
+            Redis::set($transactionKey, json_encode($response));
+            Redis::expire($transactionKey, 86400);
+
+            return $response;
+        } catch (\Exception $e) {
+            Util::WriteLog('evoplay_error', $e->getMessage());
+            return [
+                'status' => 'error',
+                'error' => [
+                    'code' => 'INTERNAL_ERROR',
+                    'message' => 'Internal server error'
+                ]
+            ];
+        }
+    }
+
+    /**
+     * 处理交易回滚请求
+     *
+     * @param string $userId 用户ID
+     * @param string $transactionId 要回滚的交易ID
+     * @param string $rollbackTransactionId 回滚交易ID
+     * @return array 处理结果
+     */
+    public function rollback($userId, $transactionId, $rollbackTransactionId)
+    {
+        try {
+            // 获取用户信息
+            $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $userId);
+            if (!$user) {
+                return [
+                    'status' => 'error',
+                    'error' => [
+                        'code' => 'USER_NOT_FOUND',
+                        'message' => 'User not found'
+                    ]
+                ];
+            }
+
+            $userId = $user->UserID;
+
+            // 检查回滚交易是否已处理
+            $rollbackKey = 'evoplay_rollback_' . $rollbackTransactionId;
+            if (Redis::exists($rollbackKey)) {
+                // 回滚已处理,返回上次的结果
+                $previousResult = json_decode(Redis::get($rollbackKey), true);
+                return $previousResult;
+            }
+
+            // 检查原始交易
+            $transactionKey = 'evoplay_tx_' . $transactionId;
+            if (!Redis::exists($transactionKey)) {
+                return [
+                    'status' => 'error',
+                    'error' => [
+                        'code' => 'TRANSACTION_NOT_FOUND',
+                        'message' => 'Original transaction not found'
+                    ]
+                ];
+            }
+
+            // 获取原始交易信息
+            $originalTransaction = json_decode(Redis::get($transactionKey), true);
+
+            // 检查是否是投注交易
+            $betKey = 'evoplay_bet_' . $transactionId;
+            if (Redis::exists($betKey)) {
+                $betInfo = Redis::get($betKey);
+                $parts = explode('_', $betInfo);
+                $gameId = $parts[0] ?? '';
+                $amount = isset($parts[1]) ? intval($parts[1]) : 0;
+
+                // 获取独占锁确保事务安全
+                $res = SetNXLock::getExclusiveLock($rollbackKey, 86400);
+                if (!$res) {
+                    // 已经处理过,返回之前的结果
+                    return $this->getBalance($userId);
+                }
+
+                // 获取当前余额
+                $currentBalance = GlobalUserInfo::getScoreByUserID($userId) * NumConfig::NUM_VALUE;
+
+                // 退还金额
+                DB::connection('write')->table('QPTreasureDB.dbo.GameScoreInfo')
+                    ->where('UserID', $userId)
+                    ->increment('Score', $amount);
+
+                // 记录取消交易
+                PlatformService::platformBet('evoplay', $gameId, -$amount, $userId);
+
+                // 更新当前余额
+                $currentBalance += $amount;
+
+                // 构造成功响应
+                $response = [
+                    'status' => 'ok',
+                    'data' => [
+                        'balance' => $currentBalance / NumConfig::NUM_VALUE,
+                        'currency' => $this->currency,
+                        'transaction_id' => $rollbackTransactionId
+                    ]
+                ];
+
+                // 缓存回滚结果
+                Redis::set($rollbackKey, json_encode($response));
+                Redis::expire($rollbackKey, 86400);
+
+                return $response;
+            } else {
+                // 如果不是投注交易,则可能是赢钱交易,处理方式类似
+                // 这里简化处理,实际应该根据Evoplay的具体要求实现
+                return [
+                    'status' => 'error',
+                    'error' => [
+                        'code' => 'UNSUPPORTED_OPERATION',
+                        'message' => 'Only bet transactions can be rolled back'
+                    ]
+                ];
+            }
+        } catch (\Exception $e) {
+            Util::WriteLog('evoplay_error', $e->getMessage());
+            return [
+                'status' => 'error',
+                'error' => [
+                    'code' => 'INTERNAL_ERROR',
+                    'message' => 'Internal server error'
+                ]
+            ];
+        }
+    }
+}

+ 107 - 0
app/Game/Services/GA4MeasurementProtocolClient.php

@@ -0,0 +1,107 @@
+<?php
+/**
+ * GA4MeasurementProtocolClient.php
+ *
+ * 一个用于通过GA4 Measurement Protocol发送事件的PHP类。
+ */
+
+class GA4MeasurementProtocolClient
+{
+    private $measurement_id;
+    private $api_secret;
+    private $firebase_app_id;
+    private $endpoint = 'https://www.google-analytics.com/mp/collect';
+
+    /**
+     * 构造函数
+     *
+     * @param string $measurement_id GA4的测量ID (G-XXXXXXX)
+     * @param string $api_secret API秘密
+     * @param string|null $firebase_app_id Firebase应用ID (可选)
+     */
+    public function __construct($measurement_id, $api_secret, $firebase_app_id = null)
+    {
+        $this->measurement_id = $measurement_id;
+        $this->api_secret = $api_secret;
+        $this->firebase_app_id = $firebase_app_id;
+    }
+
+    /**
+     * 发送事件到GA4
+     *
+     * @param string $client_id 客户端ID (通常是UUID)
+     * @param string $event_name 事件名称
+     * @param array $event_params 事件参数(键值对)
+     * @param int|null $timestamp_ms 自定义时间戳(可选)
+     *
+     * @return bool 返回true表示发送成功,false表示失败
+     */
+    public function sendEvent($client_id, $event_name, $event_params = [], $timestamp_ms = null)
+    {
+        $payload = [
+            'client_id' => $client_id,
+            'events' => [
+                [
+                    'name' => $event_name,
+                    'params' => $event_params
+                ]
+            ]
+        ];
+
+        if ($timestamp_ms) {
+            $payload['events'][0]['timestamp_micros'] = $timestamp_ms * 1000; // 转换为微秒
+        }
+
+        $url = $this->endpoint . '?measurement_id=' . $this->measurement_id . '&api_secret=' . $this->api_secret;
+
+        $response = $this->postRequest($url, $payload);
+
+        if ($response !== false) {
+            // GA4的响应状态码
+            $http_code = $response['http_code'];
+            return in_array($http_code, [200, 204]);
+        }
+
+        return false;
+    }
+
+    /**
+     * 发送POST请求
+     *
+     * @param string $url 请求URL
+     * @param array $data 请求数据
+     *
+     * @return array|false 返回包含响应的数组,或者false失败
+     */
+    private function postRequest($url, $data)
+    {
+        $ch = curl_init($url);
+
+        $payload = json_encode($data);
+
+        curl_setopt($ch, CURLOPT_POST, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, [
+            'Content-Type: application/json',
+            'Content-Length: ' . strlen($payload)
+        ]);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // 确保SSL安全
+
+        $response = curl_exec($ch);
+        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+
+        if ($response === false) {
+            error_log('cURL error: ' . curl_error($ch));
+            curl_close($ch);
+            return false;
+        }
+
+        curl_close($ch);
+
+        return [
+            'body' => $response,
+            'http_code' => $http_code
+        ];
+    }
+}

+ 105 - 0
app/Game/Services/GameEncrypt.php

@@ -0,0 +1,105 @@
+<?php
+
+namespace App\Game\Services;
+
+
+use App\Notification\TelegramBot;
+use App\Util;
+use Closure;
+
+
+use App\Game\Services\LZCompressor\LZString as LZ;
+use App\Game\Services\LZCompressor\LZUtil;
+
+class GameEncrypt
+{
+    /**
+     * 验证签名 API接口
+     * @param \Illuminate\Http\Request $request
+     * @param Closure $next
+     */
+    public function handle($request, Closure $next)
+    {
+        $hasLz=$request->has('lz');
+        $debug=$request->has('_db')&&($request->input('_db')=='g');
+        if($hasLz) {
+            try{
+                $newarr = \GuzzleHttp\json_decode(self::decrypt($request->input('lz')),true);
+                $_REQUEST=array_merge($_REQUEST,$newarr);
+                $request->replace($newarr);
+            }catch (\Exception $exception){
+
+                try{
+                    $newarr = \GuzzleHttp\json_decode(utf8_encode(self::decrypt($request->input('lz'))),true);
+                    $_REQUEST=array_merge($_REQUEST,$newarr);
+                    $request->replace($newarr);
+                }catch (\Exception $exception){
+                    TelegramBot::getDefault()->sendMsgWithEnv("Game LZ jsdecode:".$exception->getMessage());
+                }
+
+            }
+
+        }
+        $response = $next($request);
+        if($debug&&$hasLz&&is_array($response)){
+            $response['req']=$request->all();
+        }
+
+        $origin = $request->server('HTTP_ORIGIN') ?? $request->server('HTTP_REFERER') ?? '*';
+
+        if($hasLz&&!RouteService::isTestSite()&&!$debug) {
+            // 对返回内容进行加密处理
+            $content = $response->getContent();
+
+            $encryptedContent = self::encrypt($content);
+
+            // 将加密后的内容设置回响应
+            $response->setContent($encryptedContent);
+        }
+
+
+        $this->header('Access-Control-Allow-Origin', $origin);
+        $this->header('Access-Control-Allow-Headers', 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN');
+        $this->header('Access-Control-Expose-Headers', '*');
+        $this->header('Access-Control-Allow-Methods', 'GET, POST');
+        $this->header('Access-Control-Allow-Credentials', 'true');
+
+        return $response;
+    }
+    protected function header($key, $value)
+    {
+        header(sprintf('%s: %s', $key, $value));
+    }
+
+
+    public static function encrypt($str){
+        try {
+
+            $encStr = LZ::compressToBase64($str);
+
+
+        }catch (\Exception $exception){
+            TelegramBot::getDefault()->sendProgramNotify("Game LZ encrypt:", $exception->getTraceAsString());
+            $encStr=$str;
+        }
+        return $encStr;
+    }
+    public static function decrypt($str){
+        if (!isset($str)||!$str||empty($str)) {
+            # code...
+            return '';
+        }
+        $str=implode("+",explode(" ",$str));
+        try {
+            $destr=LZ::decompressFromBase64($str);
+        }catch (\Exception $exception){
+            Util::WriteLog('gamelz',$str);
+            TelegramBot::getDefault()->sendProgramNotify("Game LZ decrypt:".$str);
+            TelegramBot::getDefault()->sendProgramNotify("Game LZ decrypt:", $exception->getMessage().$exception->getTraceAsString());
+            $destr=$str;
+        }
+        return $destr;
+
+
+    }
+}

+ 91 - 0
app/Game/Services/LZCompressor/LZContext.php

@@ -0,0 +1,91 @@
+<?php
+namespace App\Game\Services\LZCompressor;
+
+class LZContext
+{
+    /**
+     * @var array
+     */
+    public $dictionary = array();
+
+    /**
+     * @var array
+     */
+    public $dictionaryToCreate = array();
+
+    /**
+     * @var string
+     */
+    public $c = '';
+
+    /**
+     * @var string
+     */
+    public $wc = '';
+
+    /**
+     * @var string
+     */
+    public $w = '';
+
+    /**
+     * @var int
+     */
+    public $enlargeIn = 2;
+
+    /**
+     * @var int
+     */
+    public $dictSize = 3;
+
+    /**
+     * @var int
+     */
+    public $numBits = 2;
+
+    /**
+     * @var LZData
+     */
+    public $data;
+
+    function __construct()
+    {
+        $this->data = new LZData;
+    }
+
+    // Helper
+
+    /**
+     * @param string $val
+     * @return bool
+     */
+    public function dictionaryContains($val) {
+        return array_key_exists($val, $this->dictionary);
+    }
+
+    /**
+     * @param $val
+     */
+    public function addToDictionary($val) {
+        $this->dictionary[$val] = $this->dictSize++;
+    }
+
+    /**
+     * @param string $val
+     * @return bool
+     */
+    public function dictionaryToCreateContains($val) {
+        return array_key_exists($val, $this->dictionaryToCreate);
+    }
+
+    /**
+     * decrements enlargeIn and extends numbits in case enlargeIn drops to 0
+     */
+    public function enlargeIn() {
+        $this->enlargeIn--;
+        if($this->enlargeIn==0) {
+            $this->enlargeIn = pow(2, $this->numBits);
+            $this->numBits++;
+        }
+    }
+}

+ 37 - 0
app/Game/Services/LZCompressor/LZData.php

@@ -0,0 +1,37 @@
+<?php
+namespace App\Game\Services\LZCompressor;
+
+class LZData
+{
+    /**
+     * @var
+     */
+    public $str = '';
+
+    /**
+     * @var
+     */
+    public $val;
+
+    /**
+     * @var int
+     */
+    public $position = 0;
+
+    /**
+     * @var int - index of letters (may be multiple of characters)
+     */
+    public $index = 1;
+
+    /*
+     * @var bool - set to true if theindex is out of str range
+     */
+    public $end = true;
+
+    /**
+     * @param unknown $str
+     */
+    public function append($str) {
+        $this->str .= $str;
+    }
+}

+ 33 - 0
app/Game/Services/LZCompressor/LZReverseDictionary.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sics
+ * Date: 28.02.2016
+ * Time: 12:53
+ */
+
+namespace App\Game\Services\LZCompressor;
+
+
+class LZReverseDictionary
+{
+
+    public $entries = array(0, 1 ,2);
+
+    public function size() {
+        return count($this->entries);
+    }
+
+    public function hasEntry($index) {
+        return array_key_exists($index, $this->entries);
+    }
+
+    public function getEntry($index) {
+        return $this->entries[$index];
+    }
+
+    public function addEntry($char) {
+        $this->entries[] = $char;
+    }
+
+}

+ 363 - 0
app/Game/Services/LZCompressor/LZString.php

@@ -0,0 +1,363 @@
+<?php
+namespace App\Game\Services\LZCompressor;
+
+class LZString
+{
+    /**
+     * Compress into a string that is already URI encoded
+     *
+     * @param string $input
+     *
+     * @return string
+     */
+    public static function compressToEncodedURIComponent($input)
+    {
+        if ($input === null) {
+            return "";
+        }
+        return self::_compress(
+            $input,
+            6,
+            function($a) {
+                return LZUtil::$keyStrUriSafe[$a];
+            }
+        );
+    }
+
+    /**
+     * Decompress from an output of compressToEncodedURIComponent
+     *
+     * @param string $input
+     *
+     * @return null|string
+     */
+    public static function decompressFromEncodedURIComponent($input)
+    {
+        if ($input === null) {
+            return "";
+        }
+        if ($input === "") {
+            return null;
+        }
+
+        $input = str_replace(' ', "+", $input);
+
+        return self::_decompress(
+            $input,
+            32,
+            function($data) {
+                $sub = substr($data->str, $data->index, 6);
+                $sub = LZUtil::utf8_charAt($sub, 0);
+                $data->index += strlen($sub);
+                $data->end = strlen($sub) <= 0;
+                return LZUtil::getBaseValue( LZUtil::$keyStrUriSafe, $sub );
+            });
+    }
+
+    public static function compressToBase64($input)
+    {
+        $res = self::_compress($input, 6, function($a) {
+            return LZUtil::$keyStrBase64[$a];
+        });
+        switch (strlen($res) % 4) { // To produce valid Base64
+            default: // When could this happen ?
+            case 0 : return $res;
+            case 1 : return $res ."===";
+            case 2 : return $res ."==";
+            case 3 : return $res ."=";
+        }
+    }
+
+    public static function decompressFromBase64($input)
+    {
+        return self::_decompress($input, 32, function($data) {
+            $sub = substr($data->str, $data->index, 6);
+            $sub = LZUtil::utf8_charAt($sub, 0);
+            $data->index += strlen($sub);
+            $data->end = strlen($sub) <= 0;
+            return LZUtil::getBaseValue(LZUtil::$keyStrBase64, $sub);
+        });
+    }
+
+    public static function compressToUTF16($input) {
+        return self::_compress($input, 15, function($a) {
+                return LZUtil16::fromCharCode($a+32);
+            }) . LZUtil16::utf16_chr(32);
+    }
+
+    public static function decompressFromUTF16($input) {
+        return self::_decompress($input, 16384, function($data) {
+            return LZUtil16::charCodeAt($data)-32;
+        });
+    }
+
+    /**
+     * @param string $uncompressed
+     * @return string
+     */
+    public static function compress($uncompressed)
+    {
+        return self::_compress($uncompressed, 16, function($a) {
+            return LZUtil::fromCharCode($a);
+        });
+    }
+
+    /**
+     * @param string $compressed
+     * @return string
+     */
+    public static function decompress($compressed)
+    {
+        return self::_decompress($compressed, 32768, function($data) {
+            $sub = substr($data->str, $data->index, 16);
+            $sub = LZUtil::utf8_charAt($sub, 0);
+            $data->index += strlen($sub);
+            $data->end = strlen($sub) <= 0;
+            return LZUtil::charCodeAt($sub, 0);
+        });
+    }
+
+    /**
+     * @param string $uncompressed
+     * @param integer $bitsPerChar
+     * @param callable $getCharFromInt
+     * @return string
+     */
+    private static function _compress($uncompressed, $bitsPerChar, $getCharFromInt) {
+
+        if(!is_string($uncompressed) || strlen($uncompressed) === 0) {
+            return '';
+        }
+
+        $context = new LZContext();
+        $length = 0;
+        $ii = 0;
+        do {
+            // take the context symbol in UTF-8
+            $sub = substr( $uncompressed, $ii, 6); // cover the full utf-8 character space
+            $context->c = mb_substr( $sub, 0, 1, 'UTF-8'); // fast take the character
+            $length = strlen( $context->c ); // get amount of bytes taken
+            $ii += $length; // advance the index
+            // handle the compression
+            if(!$context->dictionaryContains($context->c)) {
+                $context->addToDictionary($context->c);
+                $context->dictionaryToCreate[$context->c] = true;
+            }
+            $context->wc = $context->w . $context->c;
+            if($context->dictionaryContains($context->wc)) {
+                $context->w = $context->wc;
+            } else {
+                self::produceW($context, $bitsPerChar, $getCharFromInt);
+            }
+        } while( $length > 0 );
+
+        if($context->w !== '') {
+            self::produceW($context, $bitsPerChar, $getCharFromInt);
+        }
+
+        $value = 2;
+        for($i=0; $i<$context->numBits; $i++) {
+            self::writeBit($value&1, $context->data, $bitsPerChar, $getCharFromInt);
+            $value = $value >> 1;
+        }
+
+        while (true) {
+            $context->data->val = $context->data->val << 1;
+            if ($context->data->position == ($bitsPerChar-1)) {
+                $context->data->append($getCharFromInt($context->data->val));
+                break;
+            }
+            $context->data->position++;
+        }
+
+        return $context->data->str;
+    }
+
+    /**
+     * @param LZContext $context
+     * @param integer $bitsPerChar
+     * @param callable $getCharFromInt
+     *
+     * @return LZContext
+     */
+    private static function produceW(LZContext $context, $bitsPerChar, $getCharFromInt)
+    {
+        if($context->dictionaryToCreateContains($context->w)) {
+            if(LZUtil::charCodeAt($context->w)<256) {
+                for ($i=0; $i<$context->numBits; $i++) {
+                    self::writeBit(null, $context->data, $bitsPerChar, $getCharFromInt);
+                }
+                $value = LZUtil::charCodeAt($context->w);
+                for ($i=0; $i<8; $i++) {
+                    self::writeBit($value&1, $context->data, $bitsPerChar, $getCharFromInt);
+                    $value = $value >> 1;
+                }
+            } else {
+                $value = 1;
+                for ($i=0; $i<$context->numBits; $i++) {
+                    self::writeBit($value, $context->data, $bitsPerChar, $getCharFromInt);
+                    $value = 0;
+                }
+                $value = LZUtil::charCodeAt($context->w);
+                for ($i=0; $i<16; $i++) {
+                    self::writeBit($value&1, $context->data, $bitsPerChar, $getCharFromInt);
+                    $value = $value >> 1;
+                }
+            }
+            $context->enlargeIn();
+            unset($context->dictionaryToCreate[$context->w]);
+        } else {
+            $value = $context->dictionary[$context->w];
+            for ($i=0; $i<$context->numBits; $i++) {
+                self::writeBit($value&1, $context->data, $bitsPerChar, $getCharFromInt);
+                $value = $value >> 1;
+            }
+        }
+        $context->enlargeIn();
+        $context->addToDictionary($context->wc);
+        $context->w = $context->c.'';
+    }
+
+    /**
+     * @param string $value
+     * @param LZData $data
+     * @param integer $bitsPerChar
+     * @param callable $getCharFromInt
+     */
+    private static function writeBit($value, LZData $data, $bitsPerChar, $getCharFromInt)
+    {
+        if(null !== $value) {
+            $data->val = ($data->val << 1) | $value;
+        } else {
+            $data->val = ($data->val << 1);
+        }
+        if ($data->position == ($bitsPerChar-1)) {
+            $data->position = 0;
+            $data->append($getCharFromInt($data->val));
+            $data->val = 0;
+        } else {
+            $data->position++;
+        }
+    }
+
+    /**
+     * @param LZData $data
+     * @param integer $resetValue
+     * @param callable $getNextValue
+     * @param integer $exponent
+     * @param string $feed
+     * @return integer
+     */
+    private static function readBits(LZData $data, $resetValue, $getNextValue, $exponent)
+    {
+        $bits = 0;
+        $maxPower = pow(2, $exponent);
+        $power=1;
+        while($power != $maxPower) {
+            $resb = $data->val & $data->position;
+            $data->position >>= 1;
+            if ($data->position == 0) {
+                $data->position = $resetValue;
+                $data->val = $getNextValue($data);
+            }
+            $bits |= (($resb>0 ? 1 : 0) * $power);
+            $power <<= 1;
+        }
+        return $bits;
+    }
+
+    /**
+     * @param string $compressed
+     * @param integer $resetValue
+     * @param callable $getNextValue
+     * @return string
+     */
+    private static function _decompress($compressed, $resetValue, $getNextValue)
+    {
+        if(!is_string($compressed) || strlen($compressed) === 0) {
+            return '';
+        }
+
+        $entry = null;
+        $enlargeIn = 4;
+        $numBits = 3;
+        $result = '';
+
+        $dictionary = new LZReverseDictionary();
+
+        $data = new LZData();
+        $data->str = $compressed;
+        $data->index = 0;
+        $data->end = false;
+        $data->val = $getNextValue($data);
+        $data->position = $resetValue;
+
+        $next = self::readBits($data, $resetValue, $getNextValue, 2);
+
+        if($next < 0 || $next > 1) {
+            return '';
+        }
+
+        $exponent = ($next == 0) ? 8 : 16;
+        $bits = self::readBits($data, $resetValue, $getNextValue, $exponent);
+
+        $c = LZUtil::fromCharCode($bits);
+        $dictionary->addEntry($c);
+        $w = $c;
+
+        $result .= $c;
+
+        while(true) {
+            if($data->end) {
+                return '';
+            }
+            $bits = self::readBits($data, $resetValue, $getNextValue, $numBits);
+
+            $c = $bits;
+
+            switch($c) {
+                case 0:
+                    $bits = self::readBits($data, $resetValue, $getNextValue, 8);
+                    $c = $dictionary->size();
+                    $dictionary->addEntry(LZUtil::fromCharCode($bits));
+                    $enlargeIn--;
+                    break;
+                case 1:
+                    $bits = self::readBits($data, $resetValue, $getNextValue, 16);
+                    $c = $dictionary->size();
+                    $dictionary->addEntry(LZUtil::fromCharCode($bits));
+                    $enlargeIn--;
+                    break;
+                case 2:
+                    return $result;
+                    break;
+            }
+
+            if($enlargeIn == 0) {
+                $enlargeIn = pow(2, $numBits);
+                $numBits++;
+            }
+
+            if($dictionary->hasEntry($c)) {
+                $entry = $dictionary->getEntry($c);
+            }
+            else {
+                if ($c == $dictionary->size()) {
+                    $entry = $w . $w[0];
+                } else {
+                    return null;
+                }
+            }
+
+            $result .= $entry;
+            $dictionary->addEntry($w . LZUtil::utf8_charAt($entry, 0));
+            $w = $entry;
+
+            $enlargeIn--;
+            if($enlargeIn == 0) {
+                $enlargeIn = pow(2, $numBits);
+                $numBits++;
+            }
+        }
+    }
+}

+ 110 - 0
app/Game/Services/LZCompressor/LZUtil.php

@@ -0,0 +1,110 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sics
+ * Date: 27.02.2016
+ * Time: 15:54
+ */
+
+namespace App\Game\Services\LZCompressor;
+
+
+class LZUtil
+{
+    /**
+     * @var string
+     */
+    public static $keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+    public static $keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
+    private static $baseReverseDic = array();
+
+    /**
+     * @param string $alphabet
+     * @param integer $character
+     * @return string
+     */
+    public static function getBaseValue($alphabet, $character)
+    {
+        if(!array_key_exists($alphabet, self::$baseReverseDic)) {
+            self::$baseReverseDic[$alphabet] = array();
+            for($i=0; $i<strlen($alphabet); $i++) {
+                self::$baseReverseDic[$alphabet][$alphabet[$i]] = $i;
+            }
+        }
+        return self::$baseReverseDic[$alphabet][$character];
+    }
+
+    /**
+     * @return string
+     */
+    public static function fromCharCode()
+    {
+        return array_reduce(func_get_args(), function ($a, $b) {
+            $a .= self::utf8_chr($b);
+            return $a;
+        });
+    }
+
+    /**
+     * Phps chr() equivalent for UTF-8 encoding
+     *
+     * @param int|string $u
+     * @return string
+     */
+    public static function utf8_chr($u)
+    {
+        return mb_convert_encoding('&#' . intval($u) . ';', 'UTF-8', 'HTML-ENTITIES');
+    }
+
+    /**
+     * @param string $str
+     * @param int $num
+     *
+     * @return bool|integer
+     */
+    public static function charCodeAt($str, $num=0)
+    {
+        return self::utf8_ord(self::utf8_charAt($str, $num));
+    }
+
+    /**
+     * @param string $ch
+     *
+     * @return bool|integer
+     */
+    public static function utf8_ord($ch)
+    {
+        // must remain php's strlen
+        $len = strlen($ch);
+        if ($len <= 0) {
+            return -1;
+        }
+        $h = ord($ch[0]);
+        if ($h <= 0x7F) return $h;
+        if ($h < 0xC2) return -3;
+        if ($h <= 0xDF && $len > 1) return ($h & 0x1F) << 6 | (ord($ch[1]) & 0x3F);
+        if ($h <= 0xEF && $len > 2) return ($h & 0x0F) << 12 | (ord($ch[1]) & 0x3F) << 6 | (ord($ch[2]) & 0x3F);
+        if ($h <= 0xF4 && $len > 3)
+            return ($h & 0x0F) << 18 | (ord($ch[1]) & 0x3F) << 12 | (ord($ch[2]) & 0x3F) << 6 | (ord($ch[3]) & 0x3F);
+        return -2;
+    }
+
+    /**
+     * @param string $str
+     * @param integer $num
+     *
+     * @return string
+     */
+    public static function utf8_charAt($str, $num)
+    {
+        return mb_substr($str, $num, 1, 'UTF-8');
+    }
+
+    /**
+     * @param string $str
+     * @return integer
+     */
+    public static function utf8_strlen($str) {
+        return mb_strlen($str, 'UTF-8');
+    }
+}

+ 94 - 0
app/Game/Services/LZCompressor/LZUtil16.php

@@ -0,0 +1,94 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sics
+ * Date: 27.02.2016
+ * Time: 15:54
+ */
+
+namespace App\Game\Services\LZCompressor;
+
+
+class LZUtil16
+{
+
+    /**
+     * @return string
+     */
+    public static function fromCharCode()
+    {
+        return array_reduce(func_get_args(), function ($a, $b) {
+            $a .= self::utf16_chr($b);
+            return $a;
+        });
+    }
+
+    /**
+     * Phps chr() equivalent for UTF-16 encoding
+     *
+     * @param int|string $u
+     * @return string
+     */
+    public static function utf16_chr($u)
+    {
+        return mb_convert_encoding('&#' . intval($u) . ';', 'UTF-16', 'HTML-ENTITIES');
+    }
+
+    /**
+     * @param string $str
+     * @param int $num
+     *
+     * @return bool|integer
+     */
+    public static function charCodeAt($data)
+    {
+        $sub = substr($data->str, $data->index, 2);
+        $sub = self::utf16_charAt($sub, 0);
+        $data->index += strlen($sub);
+        $data->end = strlen($sub) <= 0;
+        return self::utf16_ord($sub);
+    }
+
+    /**
+     * @source http://blog.sarabande.jp/post/35970262740
+     * @param string $ch
+     * @return bool|integer
+     */
+    public static function utf16_ord($ch) {
+        $length = strlen($ch);
+        if (2 === $length) {
+            return hexdec(bin2hex($ch));
+        } else if (4 === $length) {
+            $w1 = $ch[0].$ch[1];
+            $w2 = $ch[2].$ch[3];
+            if ($w1 < "\xD8\x00" || "\xDF\xFF" < $w1 || $w2 < "\xDC\x00" || "\xDF\xFF" < $w2) {
+                return false;
+            }
+            $w1 = (hexdec(bin2hex($w1)) & 0x3ff) << 10;
+            $w2 =  hexdec(bin2hex($w2)) & 0x3ff;
+            return $w1 + $w2 + 0x10000;
+        }
+        return false;
+    }
+
+    /**
+     * @param string $str
+     * @param integer $num
+     *
+     * @return string
+     */
+    public static function utf16_charAt($str, $num)
+    {
+        return mb_substr($str, $num, 1, 'UTF-16');
+    }
+
+    /**
+     * @param string $str
+     * @return integer
+     */
+    public static function utf16_strlen($str)
+    {
+        return mb_strlen($str, 'UTF-16');
+    }
+
+}

+ 466 - 0
app/Game/Services/LuckyStreakService.php

@@ -0,0 +1,466 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Game\GlobalUserInfo;
+use App\Http\helper\NumConfig;
+use App\Models\AccountsInfo;
+use App\Notification\TelegramBot;
+use App\Util;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+
+class LuckyStreakService
+{
+    protected $client;
+    protected $operatorId;
+    protected $operatorName;
+    protected $clientId;
+    protected $clientSecret;
+    protected $apiUrl;
+    protected $currency;
+    protected $hmacId;
+    protected $hmacUser;
+    protected $hmacKey;
+
+    public function __construct()
+    {
+        $this->client = new Client();
+        $this->currency = env('CONFIG_24680_CURRENCY');
+        $this->operatorId = env('LUCKYSTREAK_OPERATOR_ID', '526');
+        $this->operatorName = env('LUCKYSTREAK_OPERATOR_NAME', 'AEG');
+        $this->clientId = env('LUCKYSTREAK_CLIENT_ID', 'AEG_operator');
+        $this->clientSecret = env('LUCKYSTREAK_CLIENT_SECRET', 'X6vuibvYJI2Z9NglbzZE');
+        $this->apiUrl = env('LUCKYSTREAK_API_URL', 'https://integ.livepbt.com');
+        $this->hmacId = env('LUCKYSTREAK_HMAC_ID', 'AEG1');
+        $this->hmacUser = env('LUCKYSTREAK_HMAC_USER', 'AEGhmac1');
+        $this->hmacKey = env('LUCKYSTREAK_HMAC_KEY', 'WcVIR7szsyXNPstaY2JX');
+    }
+
+    public function callSubApi($username,$request)
+    {
+        Util::WriteLog('luckystreak_validate','callsubapi');
+        $apiurl=ServerService::GetApiByGUID($username);
+
+        try {
+            // 获取当前请求的 GET 和 POST 数据
+//            $getData = $request->query(); // 获取 GET 数据
+            $postData = $request->post(); // 获取 POST 数据
+
+            $response = $this->client->post( $apiurl . $_SERVER['REQUEST_URI'], [
+                'verify'=>false,
+                //                'query' => $getData, // 传递 GET 数据
+                'form_params' => $postData, // 传递 POST 数据
+            ]);
+            $res=json_decode($response->getBody(),true);
+            Util::WriteLog('luckystreak_validate',$res);
+            return $res;
+        } catch (RequestException $e) {
+            return $this->handleRequestException($e);
+        }
+    }
+
+    private function handleRequestException(\Exception $e)
+    {
+//        TelegramBot::getDefault()->sendMsgWithEnv($e->getMessage().$e->getTraceAsString());
+    }
+
+    /**
+     * 获取授权令牌
+     *
+     * @return string|null
+     */
+    public function getAuthToken()
+    {
+        try {
+            // 构建凭证字符串
+            $credentialsBase = $this->operatorId . ':' . $this->clientId . ':' . $this->clientSecret;
+            $credentials = base64_encode($credentialsBase);
+
+            // 请求授权令牌
+            $response = $this->client->post('https://integ-api-ids.livepbt.com/ids/connect/token', [
+                'verify' => false,
+                'headers' => [
+                    'Content-Type' => 'application/x-www-form-urlencoded',
+                    'Authorization'=>'Basic QUVHX29wZXJhdG9yOlg2dnVpYnZZSkkyWjlOZ2xielpF'
+                ],
+                'form_params' => [
+                    'grant_type' => 'operator_authorization',
+                    'scope' => 'operator offline_access',
+                    'operator_name' => 'AEG'
+                ]
+            ]);
+
+            $result = json_decode($response->getBody(), true);
+            $token = $result['access_token'] ?? null;
+
+            if ($token) {
+                // 缓存令牌一段时间以减少API调用
+                $expiresIn = $result['expires_in'] ?? 3600;
+                Redis::setex('luckystreak_token', $expiresIn - 60, $token);
+            }
+
+            return $token;
+
+        } catch (\Exception $e) {
+            Util::WriteLog('luckystreak_error', $e->getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 获取缓存的令牌或请求新令牌
+     *
+     * @return string|null
+     */
+    public function getCachedAuthToken()
+    {
+        // 尝试从缓存获取令牌
+        $token = Redis::get('luckystreak_token');
+
+        // 如果缓存中没有令牌或令牌已过期,请求新令牌
+        if (!$token) {
+            $token = $this->getAuthToken();
+        }
+
+        return $token;
+    }
+
+    /**
+     * 获取游戏列表
+     *
+     * @return array
+     */
+    public function getGamesList()
+    {
+        try {
+            // 获取授权令牌
+            $token = $this->getCachedAuthToken();
+            if (!$token) {
+                throw new \Exception("Failed to get authorization token");
+            }
+
+            // 调用游戏列表API
+            $response = $this->client->post($this->apiUrl . '/lobby/api/v4/lobby/games', [
+                'verify' => false,
+                'headers' => [
+                    'Authorization' => 'Bearer ' . $token,
+                    'Content-Type' => 'application/json'
+                ],
+                'json' => [
+                    'data' => [
+                        'Open' => true,
+                        'GameTypes' => [],
+                        'Currencies' => []
+                    ]
+                ]
+            ]);
+
+            return json_decode($response->getBody(), true);
+
+        } catch (\Exception $e) {
+            Util::WriteLog('luckystreak_error', $e->getMessage());
+            return ['error' => $e->getMessage()];
+        }
+    }
+    public function getJackpot()
+    {
+        try {
+            // 获取授权令牌
+            $token = $this->getCachedAuthToken();
+            if (!$token) {
+                throw new \Exception("Failed to get authorization token");
+            }
+
+            // 调用游戏列表API
+            $response = $this->client->post($this->apiUrl . '/lobby/api/v4/lobby/jackpots', [
+                'verify' => false,
+                'headers' => [
+                    'Authorization' => 'Bearer ' . $token,
+                    'Content-Type' => 'application/json'
+                ],
+                'json' => [
+                ]
+            ]);
+
+            return json_decode($response->getBody(), true);
+
+        } catch (\Exception $e) {
+            Util::WriteLog('luckystreak_error', $e->getMessage());
+            return ['error' => $e->getMessage()];
+        }
+    }
+
+    /**
+     * 获取游戏启动URL的HTML
+     *
+     * @param string $gameId
+     * @param string $globalUid
+     * @param string $ipAddress
+     * @param string $language
+     * @return string
+     */
+    public function getLaunchURLHTML($gameId,$globalUid, $ipAddress, $language)
+    {
+        try {
+            // 获取游戏类型(如果需要)
+            $gameType = $this->getGameTypeById($gameId);
+
+            // 使用GlobalUID作为AuthCode参数
+            // 使用正确的启动URL格式
+            $launchURL = "https://m.integ.livepbt.com/?PlayerName={$globalUid}&OperatorName=AEG&AuthCode={$globalUid}&GameId={$gameId}&GameType={$gameType}&LimitsGroupId=67efe1d102d4c16717303002";
+
+
+            // 添加语言参数
+            if ($language) {
+                $launchURL .= "&language={$language}";
+            }
+
+            // 返回HTML重定向脚本
+            return "<script>window.location.href='{$launchURL}';</script>";
+
+        } catch (\Exception $e) {
+            Util::WriteLog('luckystreak_error', $e->getMessage());
+            return "<script>alert('System error: " . $e->getMessage() . "');</script>";
+        }
+    }
+
+    /**
+     * 根据游戏ID获取游戏类型
+     *
+     * @param string $gameId
+     * @return string|null
+     */
+    private function getGameTypeById($gameId)
+    {
+        // 基本游戏类型映射
+        $gameTypes = [
+            '3' => 'Roulette',
+            '4' => 'Blackjack',
+            '2' => 'Baccarat',
+            // 可以添加更多游戏类型映射
+        ];
+
+        return $gameTypes[$gameId] ?? null;
+    }
+
+    /**
+     * 验证HMAC签名(用于出站请求)
+     *
+     * @param array $data
+     * @param string $signature
+     * @return bool
+     */
+    public function verifyHmacSignature($data, $signature)
+    {
+        // 根据LuckyStreak API文档实现HMAC验证逻辑
+        // 按照文档附录B的规范实现
+
+        // 1. 获取完整请求URL中的路径部分(不包含域名和查询参数)
+        $path = $data['path'] ?? '';
+        unset($data['path']);
+
+        // 2. 获取请求方法
+        $method = $data['method'] ?? 'POST';
+        unset($data['method']);
+
+        // 3. 获取请求时间戳
+        $timestamp = $data['timestamp'] ?? '';
+        if (empty($timestamp)) {
+            return false;
+        }
+        unset($data['timestamp']);
+
+        // 4. 获取随机nonce
+        $nonce = $data['nonce'] ?? '';
+        if (empty($nonce)) {
+            return false;
+        }
+        unset($data['nonce']);
+
+        // 获取ext和hash
+        $ext = '';
+        if (isset($data['ext'])) {
+            $ext = $data['ext'];
+            unset($data['ext']);
+        }
+
+        $hash = '';
+        if (isset($data['hash'])) {
+            $hash = $data['hash'];
+            unset($data['hash']);
+        }
+
+        // 主机名和端口
+        $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'euapi.24680.org';
+        $port = '443'; // 假设使用HTTPS
+
+        // 5. 构建规范化请求字符串
+        $normalized = "hawk.1.header\n" .
+                     $timestamp . "\n" .
+                     $nonce . "\n" .
+                     $method . "\n" .
+                     $path . "\n" .
+                     $host . "\n" .
+                     $port . "\n";
+
+        // 6. 如果有请求体,添加请求体内容的哈希
+        if (isset($data['body']) && !empty($data['body'])) {
+            $body = $data['body'];
+
+            // 如果是JSON字符串,尝试解码并重新编码,确保格式一致
+            if (is_string($body) && json_decode($body) !== null) {
+                $bodyObj = json_decode($body, true);
+                if (is_array($bodyObj)) {
+                    $body = json_encode($bodyObj);
+                }
+            } elseif (is_array($body)) {
+                $body = json_encode($body);
+            }
+
+            // 如果已提供的hash值,使用它
+            if (!empty($hash)) {
+                $normalized .= $hash . "\n";
+            } else {
+                // 否则计算body的hash
+                $bodyHash = base64_encode(hash('sha256', $body, true));
+                $normalized .= $bodyHash . "\n";
+            }
+        } else {
+            $normalized .= "\n"; // 如果没有请求体,添加空行
+        }
+
+        // 7. 如果有ext,添加ext
+        if (!empty($ext)) {
+            $normalized .= $ext . "\n";
+        } else {
+            $normalized .= ''; // 如果没有ext,添加空字符串
+        }
+
+        // 8. 使用HMAC-SHA256生成签名
+        $calculatedSignature = base64_encode(hash_hmac('sha256', $normalized, $this->hmacKey, true));
+
+        // 9. 比较签名
+//        Util::WriteLog('luckystreak_hmac', [
+//            'normalized' => $normalized,
+//            'calculated' => $calculatedSignature,
+//            'received' => $signature,
+//            'hmacKey' => $this->hmacKey
+//        ]);
+
+        return $signature === $calculatedSignature;
+    }
+
+    public function generateHmacSignature($data, $body = null)
+    {
+
+//        $data=['path' => '',
+//         'method' => '',
+//         'timestamp' => '',
+//         'nonce' => ''];
+        // 生成时间戳(毫秒级)
+//        $timestamp = (string)round(microtime(true) * 1000);
+
+        // 生成随机nonce
+//        $nonce = uniqid('', true);
+
+        extract($data);
+
+        // 主机名和端口
+        $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'euapi.24680.org';
+        $port = '443'; // 假设使用HTTPS
+
+        // 请求标识符 (根据文档使用hawk.1.response)
+        $type = "hawk.1.response";
+
+        // 构建规范化请求字符串
+        $normalized = $type . "\n" .
+                     $timestamp . "\n" .
+                     $nonce . "\n" .
+                     $method . "\n" .
+                     $path . "\n" .
+                     $host . "\n" .
+                     $port . "\n";
+
+        // 如果有请求体,添加请求体的哈希
+        $bodyHash = '';
+        if (!empty($body)&&false) {
+            // 如果是JSON字符串,尝试解码并重新编码,确保格式一致
+            if (is_string($body) && json_decode($body) !== null) {
+                $bodyObj = json_decode($body, true);
+                if (is_array($bodyObj)) {
+                    $body = json_encode($bodyObj);
+                }
+            } elseif (is_array($body)) {
+                $body = json_encode($body);
+            }
+
+            $bodyHash = base64_encode(hash('sha256', $body, true));
+            $normalized .= $bodyHash . "\n";
+        } else {
+            $normalized .= "\n"; // 如果没有请求体,添加空行
+        }
+
+        // 添加ext(根据文档使用X-Request-Header-To-Protect:secret)
+        $ext = "X-Request-Header-To-Protect:secret";
+        $normalized .= $ext ."\n";
+
+        // 使用HMAC-SHA256生成签名
+        $signature = base64_encode(hash_hmac('sha256', $normalized, $this->hmacKey, true));
+
+//        Util::WriteLog('luckystreak_hmac', [
+//            'normalized' => $normalized,
+//            'signature' => $signature,
+//            'body' => $body,
+//            'bodyHash' => $bodyHash,
+//            'header'=>[ 'Server-Authorization' => 'hawk mac="' . $signature . '", ext="' . $ext . '"']
+//        ]);
+
+        // 返回Server-Authorization头(根据文档)
+        return [
+            'Server-Authorization' => 'hawk mac="' . $signature . '", ext="' . $ext . '"'
+        ];
+    }
+
+    /**
+     * 获取第三方游戏提供商游戏列表
+     *
+     * @return array
+     */
+    public function getProviderGamesList()
+    {
+        try {
+            // 获取授权令牌
+            $token = $this->getCachedAuthToken();
+            if (!$token) {
+                throw new \Exception("Failed to get authorization token");
+            }
+
+            // 调用Provider游戏列表API
+            $response = $this->client->post($this->apiUrl . '/lobby/api/v4/lobby/providergames', [
+                'verify' => false,
+                'headers' => [
+                    'Authorization' => 'Bearer ' . $token,
+                    'Content-Type' => 'application/json'
+                ],
+                'json' => [
+                    'data' => [
+                        'Open' => true,
+                        'GameTypes' => [],
+                        'Currencies' => []
+                    ]
+                ]
+            ]);
+
+            return json_decode($response->getBody(), true);
+
+        } catch (\Exception $e) {
+            Util::WriteLog('luckystreak_error', $e->getMessage());
+            return ['error' => $e->getMessage()];
+        }
+    }
+}
+

+ 31 - 0
app/Game/Services/MustGameLogin.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Game\GlobalUserInfo;
+use App\Http\Controllers\Game\LoginController;
+use Closure;
+
+class MustGameLogin
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+
+        $user = GlobalUserInfo::$me;
+        if (!$user) {
+
+            $response=response()->json(apiReturnFail(['login.notfound', 'Sua conta não foi encontrada, registre-se ou tente novamente!'],"",404));
+            if($request->hasCookie('guuid'))$response=$response->withCookie(LoginController::clearLoginCookie());
+            return $response;
+        }
+        GlobalUserInfo::UpdateLoginDate($request);
+        return $next($request);
+    }
+}

+ 116 - 0
app/Game/Services/OuroGameService.php

@@ -0,0 +1,116 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Facade\TableName;
+use App\Game\Config\GameBasicConfig;
+use App\Game\GlobalUserInfo;
+use App\Http\helper\HttpCurl;
+use App\Http\helper\NumConfig;
+use App\Models\RecordScoreInfo;
+use App\Notification\TelegramBot;
+use App\Util;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+use Yansongda\Pay\Log;
+
+class OuroGameService
+{
+    const REASON_updateAccount=83;
+    const REASON_PwaBonus=82;
+    const REASON_RedEnvelope=12;
+    const REASON_BindPhone=21;
+    const REASON_AgentBonus=2;
+    const REASON_AgentWithDraw=81;
+    public static function notifyWebHall($UserID,$GlobalUID="",$cmd='pay_finish',$data=["Golds"=>0,"PayNum"=>0]){
+
+        try {
+            $url = str_replace("wss:","https:",GameBasicConfig::$HallServer).'/phpapi';
+//        aaa=111&password=wojiushimima&cmd=pay_finish&UserID=1
+            if(empty($GlobalUID)){
+                if(GlobalUserInfo::$me&&GlobalUserInfo::$me->UserID==$UserID){
+                    $user=GlobalUserInfo::$me;
+                }else{
+                    $user=GlobalUserInfo::getGameUserInfo('UserID',$UserID);
+                }
+                if(!$user)return;
+                $GlobalUID=$user->GlobalUID;
+            }
+            $query = ['aaa' => 111, 'password' => 'wojiushimima', 'cmd' => $cmd, 'UserID' => $UserID, 'GlobalUID' => $GlobalUID,'data' => json_encode($data)];
+
+            $build_query = $url . '?' . http_build_query($query);
+            Util::WriteLog("hallnoti",$build_query);
+            return (new HttpCurl())->curl_get($build_query);
+//            dd($build_query);
+        }catch (\Exception $exception){
+            $telegram = new TelegramBot();
+            $env = env('APP_ENV');
+            $telegram->sendMsg($env."24680 hallnotify error:".$exception->getMessage());
+        }
+        return false;
+    }
+    public static function notifyMail($user_id,$GlobalUID="")
+    {
+        self::notifyWebHall($user_id,$GlobalUID,'call_client',["type"=>"refresh_mail"]);
+
+    }
+    public static function getUserInGame($UserID,$GlobalUID="")
+    {
+        $key='ingame_state_'.$UserID;
+        $ingame_state=Redis::get($key)??-1;
+        return intval($ingame_state);
+    }
+    public static function AddScore($UserID,$GiftScore,$reason=null)
+    {
+        // 增加用户金币
+        $OrgScore = DB::table('QPTreasureDB.dbo.GameScoreInfo')->where('UserID', $UserID)->value('Score');
+
+        Log::info('OuroService变化金币' . $GiftScore);
+        DB::table('QPTreasureDB.dbo.GameScoreInfo')->where('UserID', $UserID)->increment('Score',$GiftScore);
+
+        $NowScore=$OrgScore+$GiftScore;
+        // 服务器通知
+
+        $url = config('transfer.stock')['url'] . 'notifyPay';
+
+        $data = [
+            'userid' =>  $UserID,
+            'getScore' => $GiftScore,
+            'score' => $NowScore,
+        ];
+
+//        (new HttpCurl())->service($url, $data);
+        self::notifyWebHall($UserID,"",'pay_finish',["Golds"=>$NowScore,"PayNum"=>$GiftScore]);
+
+        RecordScoreInfo::addScore($UserID, $GiftScore, $reason, $OrgScore); #赠送彩金
+
+        if($reason){
+            DB::connection('sqlsrv')->unprepared("
+            SET NOCOUNT ON;
+            use QPRecordDB;
+            exec QPRecordDB.dbo.GSP_YN_GR_RecordGameScore $UserID,$GiftScore,$reason,0,'',0,0");
+        }
+
+        return [$OrgScore,$NowScore];
+    }
+    public static function AddDrawBase($UserID, $draw_base, $act_no=2)
+    {
+        // 增加可提额度
+        if (DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')->where('UserID', $UserID)->first()) {
+            DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')->where('UserID', $UserID)->increment('DrawBase', $draw_base);
+        } else {
+            DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')->insert(['UserID' => $UserID, 'DrawBase' => $draw_base]);
+        }
+
+        // 增加记录
+        DB::table(TableName::agent() . 'add_draw_base')
+            ->insert([
+                'user_id' => $UserID,
+                'draw_base' => $draw_base,
+                'create_time' => now(),
+                'admin_id' => '24680'.$act_no
+            ]);
+
+
+    }
+}

+ 124 - 0
app/Game/Services/PPlayService.php

@@ -0,0 +1,124 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Util;
+use GuzzleHttp\Client;
+
+class PPlayService
+{
+    protected $baseUrl;
+    public $secretKey;
+
+    public $providerId;
+
+    public $secureLogin;
+
+    public function __construct()
+    {
+        $this->baseUrl = "https://api-spe-11.pragmaticplay.net/IntegrationService/v3/http/CasinoGameAPI";
+        $this->secretKey = "mvxCnuKq8A4GsjUn";
+        $this->providerId = "PragmaticPlay";
+        $this->secureLogin = "btcsms_bitcosmos";
+    }
+
+
+    function generateHash($params)
+    {
+        ksort($params);
+//        $queryString = http_build_query($params);
+
+        $queryString = '';
+        foreach ($params as $key => $value) {
+            $queryString .= $key . '=' . $value . '&';
+        }
+
+        // 去掉最后一个多余的 '&'
+        $queryString = rtrim($queryString, '&');
+
+
+        $secretKey = $this->secretKey;
+        $queryStringWithSecret = $queryString . $secretKey;
+        return md5($queryStringWithSecret);
+    }
+
+    function checkHash($params)
+    {
+        $hash = @$params['hash'];
+        unset($params['hash']);
+        if(isset($params['providerId'])){
+            return ($hash == $this->generateHash($params)) && ($params['providerId'] == $this->providerId);
+        }
+        return  $hash == $this->generateHash($params);
+    }
+
+
+    // 发送POST请求
+    protected function postRequest($endpoint, $data,$json = true)
+    {
+        $data['secureLogin'] = $this->secureLogin;
+        $data['hash'] = $this->generateHash($data);
+        $post = http_build_query($data);
+        $headers = ['Content-Type' => 'application/x-www-form-urlencoded'];
+        $rs = Util::curlPost2($this->baseUrl . $endpoint,$post,true,$headers);
+        Util::WriteLog('24680_pp_rs', $post);
+        Util::WriteLog('24680_pp_rs', $rs);
+        if(!$json){
+            return $rs;
+        }
+        return json_decode($rs, true);
+    }
+
+    public function getCasinoGames()
+    {
+        $data = [];
+        return $this->postRequest('/getCasinoGames/',$data);
+    }
+
+    public function getLobbyGames()
+    {
+        $data = ['categories'=>'all'];
+        return $this->postRequest('/getLobbyGames/',$data);
+    }
+
+    public function closeSession($externalPlayerId,$gameId,$clearHistory=0)
+    {
+        $data = [
+            'externalPlayerId' => $externalPlayerId,
+            'gameId' => $gameId,
+            'clearHistory' => $clearHistory
+        ];
+        return $this->postRequest('/closeSession/',$data);
+    }
+
+    public function cancelRound($externalPlayerId,$gameId,$roundId)
+    {
+        $data = [
+            'externalPlayerId' => $externalPlayerId,
+            'gameId' => $gameId,
+            'roundId' => $roundId
+        ];
+        return $this->postRequest('/cancelRound/',$data);
+    }
+
+    public function heartbeatCheck()
+    {
+        return file_get_contents($this->baseUrl.'/health/heartbeatCheck');
+    }
+
+
+    public function getLaunchURL($symbol,$token,$playerId,$currency='BRL',$lang='pt')
+    {
+        $data = [
+            'symbol' => $symbol,
+            'language' => $lang,
+            'token' => $token,
+            'externalPlayerId' => $playerId,
+            'currency' => $currency,
+            'lobbyUrl' => route('pp.lobby')
+        ];
+        return $this->postRequest('/game/url',$data);
+    }
+
+}
+

+ 124 - 0
app/Game/Services/PPlayTestService.php

@@ -0,0 +1,124 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Util;
+use GuzzleHttp\Client;
+
+class PPlayTestService
+{
+    protected $baseUrl;
+    public $secretKey;
+
+    public $providerId;
+
+    public $secureLogin;
+
+    public function __construct()
+    {
+        $this->baseUrl = "https://api.prerelease-env.biz/IntegrationService/v3/http/CasinoGameAPI";
+        $this->secretKey = "Gp5XhqasNn";
+        $this->providerId = "PragmaticPlay";
+        $this->secureLogin = "btcsms_bitcosmos";
+    }
+
+
+    function generateHash($params)
+    {
+        ksort($params);
+//        $queryString = http_build_query($params);
+
+        $queryString = '';
+        foreach ($params as $key => $value) {
+            $queryString .= $key . '=' . $value . '&';
+        }
+
+        // 去掉最后一个多余的 '&'
+        $queryString = rtrim($queryString, '&');
+
+
+        $secretKey = $this->secretKey;
+        $queryStringWithSecret = $queryString . $secretKey;
+        return md5($queryStringWithSecret);
+    }
+
+    function checkHash($params)
+    {
+        $hash = @$params['hash'];
+        unset($params['hash']);
+        if(isset($params['providerId'])){
+            return ($hash == $this->generateHash($params)) && ($params['providerId'] == $this->providerId);
+        }
+        return  $hash == $this->generateHash($params);
+    }
+
+
+    // 发送POST请求
+    protected function postRequest($endpoint, $data,$json = true)
+    {
+        $data['secureLogin'] = $this->secureLogin;
+        $data['hash'] = $this->generateHash($data);
+        $post = http_build_query($data);
+        $headers = ['Content-Type' => 'application/x-www-form-urlencoded'];
+        $rs = Util::curlPost2($this->baseUrl . $endpoint,$post,true,$headers);
+        Util::WriteLog('24680_pp_rs', $post);
+        Util::WriteLog('24680_pp_rs', $rs);
+        if(!$json){
+            return $rs;
+        }
+        return json_decode($rs, true);
+    }
+
+    public function getCasinoGames()
+    {
+        $data = [];
+        return $this->postRequest('/getCasinoGames/',$data);
+    }
+
+    public function getLobbyGames()
+    {
+        $data = [];
+        return $this->postRequest('/getLobbyGames/',$data);
+    }
+
+    public function closeSession($externalPlayerId,$gameId,$clearHistory=0)
+    {
+        $data = [
+            'externalPlayerId' => $externalPlayerId,
+            'gameId' => $gameId,
+            'clearHistory' => $clearHistory
+        ];
+        return $this->postRequest('/closeSession/',$data);
+    }
+
+    public function cancelRound($externalPlayerId,$gameId,$roundId)
+    {
+        $data = [
+            'externalPlayerId' => $externalPlayerId,
+            'gameId' => $gameId,
+            'roundId' => $roundId
+        ];
+        return $this->postRequest('/cancelRound/',$data);
+    }
+
+    public function heartbeatCheck()
+    {
+        return file_get_contents($this->baseUrl.'/health/heartbeatCheck');
+    }
+
+
+    public function getLaunchURL($symbol,$token,$playerId,$currency='BRL',$lang='pt')
+    {
+        $data = [
+            'symbol' => $symbol,
+            'language' => $lang,
+            'token' => $token,
+            'externalPlayerId' => $playerId,
+            'currency' => $currency,
+            'lobbyUrl' => route('pp.lobby')
+        ];
+        return $this->postRequest('/game/url',$data);
+    }
+
+}
+

+ 98 - 0
app/Game/Services/PgSoftService.php

@@ -0,0 +1,98 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Util;
+use GuzzleHttp\Client;
+
+class PgSoftService
+{
+    protected $client;
+    protected $baseUrl;
+    public $secretKey;
+
+    public $token;
+
+    public $salt;
+
+    public function __construct()
+    {
+        //N-cf_29a0
+        $this->client = new Client();
+        $this->baseUrl = "https://api.pg-bo.co";
+        $this->secretKey = "29a03bca22da48b0806b5854e59148c8";
+        $this->token = "N-cfa64541-6c36-4507-b221-a7128d7d5364";
+        $this->salt = "d1a3466f5d5e46ad9f97069bc63b1b0a";
+//        $this->client = new Client();
+//        $this->baseUrl = "https://api.pg-bo.me";
+//        $this->secretKey = "98d2e7bd8d6a49588a68ee25f0072e4f";
+//        $this->token = "I-226f37ece4604f19a926c0cd709a995c";
+//        $this->salt = "ec064b935d794d49b8a31062420d451a";
+    }
+
+
+    function generateGUID() {
+        // Generate 16 random bytes
+        $data = openssl_random_pseudo_bytes(16);
+
+        // Set the version to 4 (random UUID)
+        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
+        // Set the variant to DCE 1.1
+        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
+
+        // Format the byte data into a GUID string
+        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
+    }
+
+
+    // 发送POST请求
+    protected function postRequest($endpoint, $data,$json = true)
+    {
+        $post = http_build_query($data);
+        $headers = ['Content-Type' => 'application/x-www-form-urlencoded'];
+        $rs = Util::curlPost2($this->baseUrl . $endpoint.'?trace_id='.$this->generateGUID(),$post,true,$headers);
+        Util::WriteLog('24680_pg_rs', $post);
+        Util::WriteLog('24680_pg_rs', $rs);
+        if(!$json){
+            return $rs;
+        }
+        return json_decode($rs, true);
+    }
+
+    public function getGamesList($lang='pt',$currency=null)
+    {
+        $currency=$currency||env('CONFIG_24680_CURRENCY');
+        $data = [
+            'operator_token' => $this->token,
+            'secret_key' => $this->secretKey,
+            'currency' => $currency,
+            'language' => $lang,
+            'status' => 1
+        ];
+        return $this->postRequest('/external/Game/v2/Get',$data);
+    }
+
+    public function getLaunchURLHTML($gameId,$playerId,$ip='',$lang='zh')
+    {
+        ///external-game-launcher/api/v1/GetLaunchURLHTML
+        $extraArgs = [
+            'btt' => 1,
+            'ops' => $playerId,
+            'l' => $lang,
+        ];
+        $data = [
+            'operator_token' => $this->token,
+            'path' => "/{$gameId}/index.html",
+            'extra_args' => http_build_query($extraArgs),
+            'url_type' => 'game-entry',
+            'client_ip' => $ip
+        ];
+        return $this->postRequest('/external-game-launcher/api/v1/GetLaunchURLHTML',$data,false);
+    }
+
+    public function verifySession($token,$secretKey)
+    {
+        return $token == $this->token && $secretKey == $this->secretKey;
+    }
+}
+

+ 91 - 0
app/Game/Services/PgSoftTestService.php

@@ -0,0 +1,91 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Util;
+use GuzzleHttp\Client;
+
+class PgSoftTestService
+{
+    protected $client;
+    protected $baseUrl;
+    public $secretKey;
+
+    public $token;
+
+    public $salt;
+
+    public function __construct()
+    {
+        $this->client = new Client();
+        $this->baseUrl = "https://api.pg-bo.me";
+        $this->secretKey = "98d2e7bd8d6a49588a68ee25f0072e4f";
+        $this->token = "I-226f37ece4604f19a926c0cd709a995c";
+        $this->salt = "ec064b935d794d49b8a31062420d451a";
+    }
+
+
+    function generateGUID() {
+        // Generate 16 random bytes
+        $data = openssl_random_pseudo_bytes(16);
+
+        // Set the version to 4 (random UUID)
+        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
+        // Set the variant to DCE 1.1
+        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
+
+        // Format the byte data into a GUID string
+        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
+    }
+
+
+    // 发送POST请求
+    protected function postRequest($endpoint, $data,$json = true)
+    {
+        $post = http_build_query($data);
+        $headers = ['Content-Type' => 'application/x-www-form-urlencoded'];
+        $rs = Util::curlPost2($this->baseUrl . $endpoint.'?trace_id='.$this->generateGUID(),$post,true,$headers);
+        Util::WriteLog('24680_pg_rs', $post);
+        Util::WriteLog('24680_pg_rs', $rs);
+        if(!$json){
+            return $rs;
+        }
+        return json_decode($rs, true);
+    }
+
+    public function getGamesList($lang='pt',$currency='BRL')
+    {
+        $data = [
+            'operator_token' => $this->token,
+            'secret_key' => $this->secretKey,
+            'currency' => $currency,
+            'language' => $lang,
+            'status' => 1
+        ];
+        return $this->postRequest('/external/Game/v2/Get',$data);
+    }
+
+    public function getLaunchURLHTML($gameId,$playerId,$ip='',$lang='zh')
+    {
+        ///external-game-launcher/api/v1/GetLaunchURLHTML
+        $extraArgs = [
+            'btt' => 1,
+            'ops' => $playerId,
+            'l' => $lang,
+        ];
+        $data = [
+            'operator_token' => $this->token,
+            'path' => "/{$gameId}/index.html",
+            'extra_args' => http_build_query($extraArgs),
+            'url_type' => 'game-entry',
+            'client_ip' => $ip
+        ];
+        return $this->postRequest('/external-game-launcher/api/v1/GetLaunchURLHTML',$data,false);
+    }
+
+    public function verifySession($token,$secretKey)
+    {
+        return $token == $this->token && $secretKey == $this->secretKey;
+    }
+}
+

+ 191 - 0
app/Game/Services/PlatformService.php

@@ -0,0 +1,191 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Facade\TableName;
+use App\Util;
+use Illuminate\Support\Facades\Redis;
+use Illuminate\Support\Facades\DB;
+
+class PlatformService
+{
+
+
+    public static function platformBet($platform, $game, $bet, $uid)
+    {
+        self::platformAllBet($platform, $bet);
+        self::platformDayBet($platform, $bet);
+        if ($game)
+            self::platformSubDayBet($platform, $game, $bet);
+        if ($bet > 0) {
+            self::platformDayPlay($platform, $uid);
+            if ($game)
+                self::platformSubDayPlay($platform, $game, $uid);
+        }
+        return true;
+    }
+
+    public static function platformWin($UserID, $platform, $game, $win, $bet, $score = 0, $addScore = true)
+    {
+
+        self::userPlatformWin($platform, $UserID, $win - $bet);
+
+        $time = time();
+        $strUniqueCode = "$platform|$game";
+        $gameid = ['atmosfera' => 81, 'betby' => 80, 'PP' => 83,'pp' => 83, 'pg' => 84, 'only' => 85, 'aviatrix' => 86,'luckystreak'=>87][$platform] . (is_numeric($game) ? $game : 0);
+
+        $lBeforeScore = $score - $win;
+        $lChangeScore = $win - $bet;
+        try {
+            $dbh = DB::connection()->getPdo();
+
+            $stmt = $dbh->prepare("exec QPTreasureDB.dbo.GSP_GR_RecordScoreInfo $UserID,$lBeforeScore,$lChangeScore,$gameid,0,'$strUniqueCode'");
+            $stmt->execute();
+
+        } catch (\Exception $exception) {
+            Util::WriteLog('pw', $exception->getMessage() . "\nexec QPTreasureDB.dbo.GSP_GR_RecordScoreInfo $UserID,$lBeforeScore,$lChangeScore,$gameid,0,'$strUniqueCode'");
+        }
+
+//        if($win<=0){
+//            return false;
+//        }
+        if ($addScore && $win > 0) {
+            DB::connection('write')->table('QPTreasureDB.dbo.GameScoreInfo')->where('UserID', $UserID)->increment('Score', $win);
+        }
+
+        if ($bet) {
+//            $addDraw = $win-$bet;
+            $addDraw = floor($bet / 5);
+            //用户输赢数据
+
+            // 增加可提额度
+            if ($totalData = DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')->where('UserID', $UserID)->exists()) {
+//                if($totalData->DrawBase+$addDraw>$score){
+//                    $addDraw = max($score - $totalData->DrawBase,0);
+//                }
+//                DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')->where('UserID', $UserID)->increment('DrawBase', $addDraw);
+                DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')->where('UserID', $UserID)->increment('DrawBase', $addDraw);
+                DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')->where('UserID', $UserID)->increment('TotalBet', $bet);
+            } else {
+//                DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')->insert(['UserID' => $UserID, 'DrawBase' => $addDraw]);
+                DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')->insert(['UserID' => $UserID, 'DrawBase' => $addDraw, 'TotalBet' => $bet]);
+            }
+//            $draw_base = intval(($win-$bet) * 0.08);
+            $draw_base = intval(($win - $bet) * 0.1);
+            if ($draw_base > 0)
+                DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')->where('UserID', $UserID)->increment('Revenue', $draw_base);
+        }
+        if ($win <= 0) {
+            return false;
+        }
+        self::platformAllWin($platform, $win);
+        self::platformDayWin($platform, $win);
+        self::platformSubDayWin($platform, $game, $win);
+        return true;
+    }
+
+
+    /**
+     * 总下注
+     * @param $platform
+     * @param $bet
+     * @return void
+     */
+
+    ###########平台总数据##################
+    public static function platformAllBet($platform, $bet)
+    {
+        $key = 'platform_' . $platform . '_bet';
+        return self::incrementOrSet($key, $bet);
+    }
+
+    public static function platformAllWin($platform, $win)
+    {
+        $key = 'platform_' . $platform . '_win';
+        return self::incrementOrSet($key, $win);
+    }
+
+
+    ###########平台每天数据################
+    public static function platformDayBet($platform, $bet)
+    {
+        $key = 'platform_' . $platform . '_bet_' . date('Ymd');
+        return self::incrementOrSet($key, $bet, 86400 * 2);
+    }
+
+    public static function platformDayWin($platform, $win)
+    {
+        $key = 'platform_' . $platform . '_win_' . date('Ymd');
+        return self::incrementOrSet($key, $win, 86400 * 2);
+    }
+
+    public static function platformDayPlay($platform, $uid)
+    {
+        $playKey = 'platform_' . $platform . '_play_' . $uid . '_' . date('Ymd');
+        if (!Redis::exists($playKey)) {
+            Redis::set($playKey, 1);
+            Redis::expire($playKey, 86400 * 2);
+            $key = 'platform_' . $platform . '_play_' . date('Ymd');
+            self::incrementOrSet($key, 1, 86400 * 2);
+        }
+        return true;
+    }
+
+
+    ###########平台子游戏每天数据################
+    public static function platformSubDayBet($platform, $game, $bet)
+    {
+        $key = 'platform_' . $platform . '_' . $game . '_bet_' . date('Ymd');
+        return self::incrementOrSet($key, $bet, 86400 * 2);
+    }
+
+    public static function platformSubDayWin($platform, $game, $win)
+    {
+        $key = 'platform_' . $platform . '_' . $game . '_win_' . date('Ymd');
+        return self::incrementOrSet($key, $win, 86400 * 2);
+    }
+
+    public static function platformSubDayPlay($platform, $game, $uid)
+    {
+        $playKey = 'platform_' . $platform . '_' . $game . '_play_' . $uid . '_' . date('Ymd');
+        if (!Redis::exists($playKey)) {
+            Redis::set($playKey, 1);
+            Redis::expire($playKey, 86400 * 2);
+            $key = 'platform_' . $platform . '_' . $game . '_play_' . date('Ymd');
+            self::incrementOrSet($key, 1, 86400 * 2);
+        }
+        return true;
+    }
+
+
+    public static function userPlatformWin($platform, $uid, $win)
+    {
+        $dkey = 'platform_' . $platform . '_' . $uid . '_win_' . date('Ymd');
+        self::incrementOrSet($dkey, $win, 86400 * 2);
+
+        $key = 'platform_' . $platform . '_' . $uid . '_win';
+        self::incrementOrSet($key, $win);
+        return true;
+    }
+
+
+    public static function incrementOrSet($key, $value, $expired = 0)
+    {
+        // 使用 Redis::exists 检查键是否存在
+        if (Redis::exists($key)) {
+            // 键存在,使用 Redis::incrby 增加相应的数值(如果 $value 是负数,则减少)
+            Redis::incrby($key, $value);
+        } else {
+            // 键不存在,使用 Redis::set 设置为当前值
+            Redis::set($key, $value);
+            if ($expired) {
+                Redis::expire($key, $expired);
+            }
+        }
+
+        // 返回当前键的值
+        return Redis::get($key);
+    }
+
+}
+

+ 156 - 0
app/Game/Services/RouteService.php

@@ -0,0 +1,156 @@
+<?php
+
+namespace App\Game\Services;
+
+
+
+
+use App\Game\AgentLinks;
+use App\Game\GlobalUserInfo;
+use App\Game\Route;
+use App\Game\RouteModel;
+use App\Game\WebChannelConfig;
+use App\Models\AccountsInfo;
+use Illuminate\Http\Request;
+
+class RouteService
+{
+    public function createRoute(array $data)
+    {
+        $model = RouteModel::create($data);
+        return $model;
+    }
+    /**
+     * 获取所有路由及其层级结构
+     *
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public function getAllRoutesWithHierarchy($StateConfig=1)
+    {
+        // 仅加载顶层路由,并预加载所有嵌套子路由
+        return RouteModel::whereNull('parent_id')
+            ->whereRaw('state&'.$StateConfig.'='.$StateConfig)
+            ->with('subs.subs.subs')  // 根据实际层级深度调整
+            ->get();
+    }
+
+    /**
+     * 通过访问的网站获取配置区分
+     * @param Request|null $request
+     * @return int
+     */
+    public  static function getStateConfig(Request $request=null)
+    {
+//        if(!$request){
+//            $origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
+//        }else{
+//            $origin = $request->server('HTTP_ORIGIN') ?? '*';
+//        }
+//
+////        $routes = $this->routeService->getAllRoutesWithHierarchy();
+//        $StateConfig=1;
+//        if(strstr($origin,"24680.pro")||strstr($origin,"localhost")){
+//            $StateConfig=2;
+//        }
+        $config=self::getChannelConfig($request);
+        $StateConfig=$config->StateNo;
+        return $StateConfig;
+    }
+
+    /**
+     * @param Request|null $request
+     * @return string
+     */
+    public static function getStateToWhereRaw(Request $request=null)
+    {
+        $StateConfig=self::getStateConfig($request);
+        return 'state&'.$StateConfig.'='.$StateConfig;
+    }
+
+    private static $_ChannelConfig=null;
+    public static function clearChannelConfig(){
+        self::$_ChannelConfig=null;
+    }
+    /**
+     * @param Request|null $request
+     * @return WebChannelConfig|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|\LaravelIdea\Helper\App\Game\_IH_WebChannelConfig_QB|mixed|object|null
+     */
+    public static function getChannelConfig(Request $request=null)
+    {
+        if(!is_null(self::$_ChannelConfig)){
+            return self::$_ChannelConfig;
+        }
+        $Channel=self::getChannel($request);
+        $config=WebChannelConfig::getByChannel($Channel);
+        if(!$config){
+            $config=WebChannelConfig::getByChannel(env('REGION_24680_DEFAULT_CHANNEL',99));
+            if(!$config)$config=WebChannelConfig::query()->first();
+        }else if($config->SHADOW_CHANNEL()!=$config->Channel){
+            $config=WebChannelConfig::getByChannel($config->SHADOW_CHANNEL());
+        }
+        self::$_ChannelConfig=$config;
+        return $config;
+    }
+    public static function getChannel(Request $request=null)
+    {
+        //从参数获取
+        if(!$request){
+            $origin = $_SERVER['HTTP_ORIGIN'] ??$_SERVER['HTTP_REFERER']?? '*';
+            if(isset($_REQUEST['c'])&&!empty($_REQUEST['c'])){
+                $Channel=$_REQUEST['c'];
+                $Channel=explode('/',$Channel)[0];
+            }
+        }else{
+            $origin = $request->server('HTTP_ORIGIN') ?? $request->server('HTTP_REFERER') ?? '*';
+            $Channel=$request->input('c','');
+            $Channel=explode('/',$Channel)[0];
+        }
+        //从用户获取
+        if(GlobalUserInfo::$me){
+            $Channel=GlobalUserInfo::$me->Channel;
+        }else if(isset($_REQUEST['act'])&&!empty($_REQUEST['act'])){
+            $ActCode = $_REQUEST['act'];
+            $link = AgentLinks::getByCode($ActCode);
+            if ($link) {
+                $Channel = $link->Channel;
+            }
+        }
+
+
+        //默认站点
+        if(!isset($Channel)||!$Channel||$Channel==env('REGION_24680_DEFAULT_CHANNEL',99)) {
+            if (strstr($origin, "24680.pro") || strstr($origin, "localhost")) {
+                $Channel = 44;
+            } else {
+                $Channel = env('REGION_24680_DEFAULT_CHANNEL',99);
+            }
+        }
+        return $Channel;
+    }
+    public static function isTestSite()
+    {
+        $origin = $_SERVER['HTTP_ORIGIN'] ??$_SERVER['HTTP_REFERER']?? '*';
+//        if(isset($_REQUEST['c'])&&!empty($_REQUEST['c'])){
+//            $Channel=$_REQUEST['c'];
+//            $Channel=explode('/',$Channel)[0];
+//        }
+        if (strstr($origin, "24680.pro")) {
+//            $Channel = 44;
+            return $origin;
+        }
+        return false;
+    }
+    public static function isTestOrLocalSite()
+    {
+        $origin = self::isTestSite();
+//        if(isset($_REQUEST['c'])&&!empty($_REQUEST['c'])){
+//            $Channel=$_REQUEST['c'];
+//            $Channel=explode('/',$Channel)[0];
+//        }
+        if (strstr($origin, "localhost")) {
+//            $Channel = 44;
+            return $origin;
+        }
+        return false;
+    }
+}

+ 162 - 0
app/Game/Services/ServerService.php

@@ -0,0 +1,162 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Util;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
+
+class ServerService
+{
+    static $example=[
+        'Bangladesh'=>'',
+        'Pakistan'=>'',
+        'Latin'=>'',
+        'OrgBrazil'=>'',
+        'Brazil'=>'',
+        'Europe'=>'',
+        'Russia'=>'',
+        'Mexico'=>'',
+        'Singapore'=>''
+    ];
+    static $codeToServer = [
+        '6f83' => [
+            'Subsite'      => 'Bangladesh',
+            'Country'      => 'Bangladesh',
+            'Currency'     => 'BDT',
+            'Lang'         => 'bn',
+            'ServerRegion' => 'ap-south-bd',
+            'Api'          => 'http://bdapi.24680.org'
+        ],
+        'b1ec' => [
+            'Subsite'      => 'Pakistan',
+            'Country'      => 'Pakistan',
+            'Currency'     => 'PKR',
+            'Lang'         => 'en',
+            'ServerRegion' => 'me-east',
+            'Api'          => 'http://pkapi.24680.org'
+        ],
+        '9bf6' => [
+            'Subsite'      => 'OrgBrazil',
+            'Country'      => 'Brazil',
+            'Currency'     => 'BRL',
+            'Lang'         => 'pt',
+            'ServerRegion' => 'sa-east-org',
+            'Api'          => 'http://api.ouro777.com'
+        ],
+        '3ef5' => [
+            'Subsite'      => 'Latin',
+            'Country'      => 'Peru_Colombia',
+            'Currency'     => 'USD',
+            'Lang'         => 'pt',
+            'ServerRegion' => 'sa-west',
+            'Api'          => 'http://saapi.24680.org'
+        ],
+        'eb1a' => [
+            'Subsite'      => 'Brazil',
+            'Country'      => 'Brazil',
+            'Currency'     => 'BRL',
+            'Lang'         => 'pt',
+            'ServerRegion' => 'sa-east',
+            'Api'          => 'http://brapi.24680.org'
+        ],
+        '90dd' => [
+            'Subsite'      => 'Europe',
+            'Country'      => 'German',
+            'Currency'     => 'EUR',
+            'Lang'         => 'en',
+            'ServerRegion' => 'eu-central',
+            'Api'          => 'http://euapi.24680.org'
+        ],
+        '720d' => [
+            'Subsite'      => 'Russia',
+            'Country'      => 'Russia',
+            'Currency'     => 'RUB',
+            'Lang'         => 'ru',
+            'ServerRegion' => 'eu-russian',
+            'Api'          => 'http://ruapi.24680.org'
+        ],
+        '2b80' => [
+            'Subsite'      => 'Mexico',
+            'Country'      => 'Mexico',
+            'Currency'     => 'MXN',
+            'Lang'         => 'es',
+            'ServerRegion' => 'na-mexico',
+            'Api'          => 'http://mxapi.24680.org'
+        ],
+
+    ];
+
+    public static function GetGlobalServerInfoByCode($code)
+    {
+
+        return self::$codeToServer[$code] ?? self::$codeToServer['90dd'];
+    }
+
+    public static function GetGlobalServerInfoBySubsite($subsite)
+    {
+        foreach (self::$codeToServer as $code => $server) {
+            if ($server['Subsite'] == $subsite) {
+                return $server;
+            }
+        }
+        return self::$codeToServer['90dd'];
+    }
+
+    public static function IsLocalUser($GlobalUID)
+    {
+        return self::GetGlobalServerInfoByGUID($GlobalUID)['ServerRegion'] == env('REGION_24680', 'sa-east');
+    }
+
+    /**
+     * @param $GlobalUID
+     * @return string[]
+     */
+    public static function GetGlobalServerInfoByGUID($GlobalUID)
+    {
+
+        ////"917c74999c-b53b-eb1a-0004478930"
+
+
+        $codes = explode("-", $GlobalUID);
+        $config = @self::$codeToServer[$codes[2]];
+        if(!$config)return self::$codeToServer['eb1a'];
+        return $config;
+
+    }
+
+    public static function GetApiByGUID($GlobalUID)
+    {
+        return self::GetGlobalServerInfoByGUID($GlobalUID)['Api'];
+    }
+
+    public static function GlobalToUserID($GlobalUID)
+    {
+        return intval(explode('-', $GlobalUID)[3]);
+    }
+
+    public static function GetLocalSign()
+    {
+        return substr(md5(env('REGION_24680', 'sa-east')),0,4);
+    }
+    public static function RedirectToSub($GlobalUID)
+    {
+
+        $apiurl = self::GetApiByGUID($GlobalUID);
+
+        // 获取当前请求的 GET 和 POST 数据
+//            $getData = $request->query(); // 获取 GET 数据
+        $postData = $_POST; // 获取 POST 数据
+        $client = new Client();
+
+        $response = $client->post($apiurl . $_SERVER['REQUEST_URI'], [
+            'verify'=>false,
+            'form_params' => $postData, // 传递 POST 数据//                'query' => $getData, // 传递 GET 数据
+        ]);
+        $res = json_decode($response->getBody(), true);
+//            Util::WriteLog('subserver',$res);
+        return $res;
+
+    }
+
+}

+ 42 - 0
app/Game/Services/TelegramAppService.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Game\Telegram\TelegramUser;
+
+class TelegramAppService
+{
+    protected $client;
+    protected $baseUrl;
+    public $secretKey;
+
+    public $token;
+
+    public $salt;
+
+    public function __construct()
+    {
+
+    }
+
+    /**
+     * @param string $hash
+     * @return TelegramUser
+     */
+    public static function decodeHash(string $hash){
+        $data=[];
+        parse_str(urldecode($hash),$data);
+        if(isset($data['user'])){
+            $user=json_decode($data['user'],true);
+            $user['chat_instance']=$data['chat_instance'];
+            $user['chat_type']=$data['chat_type'];
+
+            $teleUser=TelegramUser::query()->where('id',$user['id'])->first();
+            if(!$teleUser)$teleUser=TelegramUser::create($user);
+            $teleUser->increment('enter_times',1);
+//            dd($teleUser);
+            return $teleUser;
+        }
+        dd($data);
+    }
+}

+ 80 - 0
app/Game/Services/TonGiftsService.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Util;
+use GuzzleHttp\Client;
+
+class TonGiftsService
+{
+    protected $client;
+    protected $baseUrl;
+    public $secretKey;
+    public $id;
+
+    public function __construct()
+    {
+        $this->client = new Client();
+        $this->baseUrl = "https://HOST";
+        $this->secretKey = "";
+        $this->id = "";
+    }
+
+
+    public function register($name,$email)
+    {
+        return $this->postRequest('/api/merchants/register',['name' => $name,'email' => $email]);
+    }
+
+    public function updateTask($userIds,$taskId,$status){
+        $data = [
+            'userIds' => $userIds,
+            'taskId' => $taskId,
+            'status' => $status
+        ];
+
+        return $this->postRequest('/api/open/updateTask',$data);
+    }
+
+
+    // 发送POST请求
+    protected function postRequest($endpoint, $data,$needSignature = true)
+    {
+
+        if($needSignature){
+            if(!isset($data['t'])){
+                $data['t'] = time();
+            }
+            $data['sign'] = $this->_generateSignature($data);
+            $data['k'] = $this->id;
+        }
+        $post = json_encode($data);
+        $headers = ['Content-Type' => 'application/json'];
+        $rs = Util::curlPost2($this->baseUrl . $endpoint,$post,true,$headers);
+        Util::WriteLog('TonGIfts', $post);
+        Util::WriteLog('TonGIfts', $rs);
+
+        return json_decode($rs, true);
+    }
+
+
+    private function _generateSignature($params)
+    {
+        ksort($params);
+        $queryString = '';
+        foreach ($params as $key => $value) {
+            if(is_array($value)){
+                $value = json_encode($value);
+            }
+            $queryString .= $key . '=' . $value . '&';
+        }
+        $queryString = rtrim($queryString, '&');
+        $secretKey = $this->secretKey;
+        return hash_hmac('sha256', $queryString, $secretKey);
+
+    }
+
+
+
+}
+

+ 16 - 0
app/Game/Style.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Style extends Model
+{
+
+    protected $connection='mysql';
+
+    const TABLE = 'webgame.styles';
+    protected $table = self::TABLE;
+
+    protected $fillable = ['styleid','style'];
+}

+ 30 - 0
app/Game/Telegram/TelegramUser.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Game\Telegram;
+
+use Illuminate\Database\Eloquent\Model;
+
+class TelegramUser extends Model {
+    protected $table = 'webgame.TelegramUser';
+    protected $primaryKey = 'tid';
+    public $timestamps = false;
+
+    protected $connection='mysql';
+
+    protected $fillable = [
+        'id',
+        'UserID',
+        'GlobalUID',
+        'first_name',
+        'last_name',
+        'username',
+        'language_code',
+        'allows_write_to_pm',
+        'photo_url',
+        'chat_instance',
+        'chat_type',
+        'create_at',
+        'update_at',
+        'enter_times',
+    ];
+}

+ 72 - 0
app/Game/WebActivity.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+
+
+class WebActivity extends Model
+{
+    protected $connection='mysql';
+
+    // 指定表名,如果表名与类名的复数相同则不需要
+    protected $table = 'webgame.activity';
+
+    // 确定哪些字段可以被赋值
+    protected $fillable = ['img','img_pt','img_es', 'title', 'link_game', 'link_module'];
+
+
+    // 隐藏不需要返回的字段
+    protected $hidden = ['img_es', 'img_pt'];
+
+    // 关闭自动维护的时间戳,如果您没有在表中创建时间戳字段则需要
+    public $timestamps = false;
+
+    public function game()
+    {
+        return $this->belongsTo(GameCard::class, 'link_game', 'id');
+    }
+
+
+    // img 字段的访问器
+    public function getImgAttribute($value)
+    {
+        //处理多语言
+        $language=GlobalUserInfo::getLocale();
+
+        switch ($language) {
+            case 'es':
+                $value= $this->attributes['img_es'] ?? $this->attributes['img_pt'] ?? $value;
+                break;
+            case 'pt':
+                $value= $this->attributes['img_pt'] ?? $this->attributes['img_es'] ?? $value;
+                break;
+
+        }
+
+        $cdn_org=["cdn.ouro777.com","cdn.moeda777.com"];
+        $cdn_replace="24680.imgix.net";
+        $img_add_param="?auto=format,compress&cs=srgb&dpr=2&w=500";
+        $img_add_param="";
+        $value=str_replace($cdn_org,$cdn_replace,$value).$img_add_param;
+        $origin = $_SERVER['HTTP_ORIGIN'] ??$_SERVER['HTTP_REFERER']?? '*';
+        if (strstr($origin, "cereja")) {
+            $value=str_replace("banner/","banner_cereja/",$value);
+        }
+        return $value;
+    }
+
+    // 自定义序列化的数组形式
+    public function toArray()
+    {
+        $array = parent::toArray();
+
+        // 添加动态生成的 img 字段
+        $array['img'] = $this->img;
+
+        // 移除 img_es 和 img_pt 字段
+        unset($array['img_es'], $array['img_pt']);
+
+        return $array;
+    }
+}

+ 136 - 0
app/Game/WebChannelConfig.php

@@ -0,0 +1,136 @@
+<?php
+
+namespace App\Game;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Redis;
+define('SPECIAL_MODE_GUEST', 1);
+define('SPECIAL_MODE_ZERO_MONEY', 2);//注册不送钱
+define('SPECIAL_DISABLE_PROMOTE_INSTALL', 4);//提示安装
+define('SPECIAL_MODE_PWA_BONUS', 8);//安装app送钱
+define('SPECIAL_MODE_SMS_BONUS', 16);//手机验证送钱
+define('SPECIAL_MODE_MAIL_BONUS', 32);//MAIL验证送钱
+define('SPECIAL_MODE_FIRSTPAY_OFF30', 64);//首冲打开30%bonus
+class WebChannelConfig extends Model
+{
+    protected $table = 'webgame.WebChannelConfig';
+    protected $primaryKey = 'ID';
+    public $incrementing = true;
+    protected $keyType = 'int';
+    protected $connection = 'mysql';
+    protected $fillable = [
+        'Channel', 'PackageName', 'Remarks', 'StateNo','SpecialMode', 'PlatformName', 'PlatformID','LoginOpen','RegOpen','BonusArr','ShadowChannel','LightApk','FullApk'
+    ];
+
+    private static $key='web_channel_config:';
+
+    public $timestamps = false;
+
+    protected $shadow_channel=0;
+    public function SHADOW_CHANNEL()
+    {
+        if($this->shadow_channel)return $this->shadow_channel;
+        $this->shadow_channel=$this->Channel;
+        if(!empty($this->ShadowChannel)){
+            $channels=explode('|',$this->ShadowChannel);
+            $allRate=0;
+            $rates=[];
+            foreach ($channels as $channel){
+                $channel=explode('%',$channel);
+                $allRate+=$channel[0];
+                $rates[$allRate]=$channel[1];
+            }
+            $rand=mt_rand(0, 100);
+            foreach($rates as $rate=>$channel){
+                if($rand<$rate){
+                    $this->shadow_channel=$channel;
+                    return $channel;
+                }
+            }
+        }
+        return $this->shadow_channel;
+    }
+    public function BONUS_PWA()
+    {
+        return explode('|',$this->BonusArr)[3]??00;
+    }
+    public function BONUS_VERIFY_PHONE()
+    {
+        return explode('|',$this->BonusArr)[1]??00;
+    }
+    public function BONUS_REG()
+    {
+        return explode('|',$this->BonusArr)[0]??00;
+    }
+    public function BONUS_VERIFY_EMAIL()
+    {
+        return explode('|',$this->BonusArr)[2]??00;
+    }
+    public static function GuestOpen($config)
+    {
+        return ($config->SpecialMode&SPECIAL_MODE_GUEST)==SPECIAL_MODE_GUEST;
+    }
+    public function isFistPay30()
+    {
+        return ($this->SpecialMode&SPECIAL_MODE_SMS_BONUS)==SPECIAL_MODE_SMS_BONUS;
+    }
+    public function isSmsBonus()
+    {
+        return ($this->SpecialMode&SPECIAL_MODE_SMS_BONUS)==SPECIAL_MODE_SMS_BONUS;
+    }
+    public function isDisablePromote()
+    {
+        return ($this->SpecialMode&SPECIAL_DISABLE_PROMOTE_INSTALL)==SPECIAL_DISABLE_PROMOTE_INSTALL;
+    }
+    public function isPwaBonus()
+    {
+        return ($this->SpecialMode&SPECIAL_MODE_PWA_BONUS)==SPECIAL_MODE_PWA_BONUS;
+    }
+    public function isGuestOpen()
+    {
+        return ($this->SpecialMode&SPECIAL_MODE_GUEST)==SPECIAL_MODE_GUEST;
+    }
+    public function isRegZeroMoneyOpen()
+    {
+        return ($this->SpecialMode&SPECIAL_MODE_ZERO_MONEY)==SPECIAL_MODE_ZERO_MONEY;
+    }
+    // 获取特定 Channel 的记录
+    public static function getByChannel($channel)
+    {
+        $cacheKey = self::$key . $channel;
+
+        $cachedConfig = Redis::get($cacheKey);
+
+        if ($cachedConfig) {
+            return new WebChannelConfig(json_decode($cachedConfig,true));
+        }
+
+        $config = self::where('Channel', $channel)->first();
+
+        if ($config) {
+            Redis::setex($cacheKey, 60, $config->toJson());
+        }
+
+        return $config;
+    }
+    // 清除缓存
+    public static function clearCache($channel)
+    {
+        $cacheKey = self::$key . $channel;
+        Redis::del($cacheKey);
+    }
+
+    // 监听模型事件
+    protected static function boot()
+    {
+        parent::boot();
+
+        static::saved(function ($model) {
+            self::clearCache($model->Channel);
+        });
+
+        static::deleted(function ($model) {
+            self::clearCache($model->Channel);
+        });
+    }
+}

+ 223 - 0
app/Http/AppFlyerEvent/AppflyerEvent.php

@@ -0,0 +1,223 @@
+<?php
+
+
+namespace App\Http\AppFlyerEvent;
+
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class AppflyerEvent
+{
+    const Token = 'aoRrdANBXaid2HX77FxE6P';
+
+    /**
+     * @param $user_id
+     * @param $appsflyer_id
+     * @param string $appsflyer_event
+     * @param int $value
+     * @return mixed
+     */
+
+    public function event($user_id, $appsflyer_id, $appsflyer_event = '', $value = 0)
+    {
+        $packag_name = DB::connection('write')->table('QPRecordDB.dbo.RecordPackageName')
+                ->where('UserID', $user_id)
+                ->select('PackgeName')
+                ->first()->PackgeName ?? '';
+
+        // 充值用
+        if (empty($appsflyer_id)) {
+
+            $appsflyer_id = DB::connection('write')->table('QPRecordDB.dbo.RecordUserIDAdid')
+                    ->where('UserID', $user_id)
+                    ->select('adid')
+                    ->first()->adid ?? '';
+        }
+
+        if (empty($appsflyer_id)) {
+            return 'false';
+        }
+
+        $purchase_event = array(
+            'appsflyer_id' => $appsflyer_id,
+        );
+
+        $purchase_event['eventName'] = $appsflyer_event;
+
+        if ($appsflyer_event == 'af_purchase_new') {
+
+            $data['af_revenue'] = $value;
+            $data['af_currency'] = 'INR';
+            $purchase_event['eventValue'] = \GuzzleHttp\json_encode($data);
+        }
+
+        $data_string = json_encode($purchase_event);
+
+        if (is_string($data_string)) {
+            Log::channel('AppflyerEvent')->info('af请求数据:' . $data_string);
+        }
+
+        $res = $this->curl($packag_name, $data_string);
+        Log::channel('AppflyerEvent')->info('af返回结果:' . $res);
+
+
+        // 中大R事件
+        //$this->zdR($user_id, $appsflyer_id, $value, $packag_name);
+
+        return $res;
+    }
+
+    // 中大R事件
+    public function zdR($user_id, $appsflyer_id, $value)
+    {
+
+        $packag_name = DB::connection('write')->table('QPRecordDB.dbo.RecordPackageName')
+                ->where('UserID', $user_id)
+                ->select('PackgeName')
+                ->first()->PackgeName ?? '';
+
+        // 充值用
+        if (empty($appsflyer_id)) {
+
+            $appsflyer_id = DB::connection('write')->table('QPRecordDB.dbo.RecordUserIDAdid')
+                    ->where('UserID', $user_id)
+                    ->select('adid')
+                    ->first()->adid ?? '';
+        }
+
+        if (empty($appsflyer_id)) {
+            return 'false';
+        }
+
+        // 传中大R事件
+        $first = DB::connection('write')->table('QPRecordDB.dbo.RecordUserTotalStatistics')
+            ->where('UserID', $user_id)
+            ->select('Recharge')
+            ->first();
+
+        if ($first) {
+            $Recharge = (int)$first->Recharge;
+
+
+            if ($Recharge > 0 && $Recharge < 1000) {
+                $af_purchase_r1000['appsflyer_id'] = $appsflyer_id;
+                $af_purchase_r1000['eventName'] = 'af_purchase_r1000';
+                $af_purchase_r1000Data['af_revenue'] = $value;
+                $af_purchase_r1000Data['af_currency'] = 'INR';
+                $af_purchase_r1000['eventValue'] = \GuzzleHttp\json_encode($af_purchase_r1000Data);
+                $data_string = json_encode($af_purchase_r1000);
+                if (is_string($data_string)) {
+                    Log::channel('AppflyerEvent')->info($packag_name.'af小于1000 请求数据:' . $data_string);
+                }
+
+                $r = $this->curl($packag_name, $data_string);
+                Log::channel('AppflyerEvent')->info($packag_name.'af小于1000返回结果:' . $r);
+            }
+
+
+            if ($Recharge >= 1000) {
+                $af_purchase_Rr['appsflyer_id'] = $appsflyer_id;
+                $af_purchase_Rr['eventName'] = 'af_purchase_Rr';
+                $af_purchase_RrData['af_revenue'] = $value;
+                $af_purchase_RrData['af_currency'] = 'INR';
+                $af_purchase_Rr['eventValue'] = \GuzzleHttp\json_encode($af_purchase_RrData);
+                $data_string = json_encode($af_purchase_Rr);
+                if (is_string($data_string)) {
+                    Log::channel('AppflyerEvent')->info($packag_name.'af中R请求数据:' . $data_string);
+                }
+
+                $r = $this->curl($packag_name, $data_string);
+                Log::channel('AppflyerEvent')->info($packag_name.'af中R返回结果:' . $r);
+            }
+
+
+            if ($Recharge >= 10000) {
+                $af_purchase_R['appsflyer_id'] = $appsflyer_id;
+                $af_purchase_R['eventName'] = 'af_purchase_R';
+                $af_purchase_RData['af_revenue'] = $value;
+                $af_purchase_RData['af_currency'] = 'INR';
+                $af_purchase_R['eventValue'] = \GuzzleHttp\json_encode($af_purchase_RData);
+
+                $data_string = json_encode($af_purchase_R);
+                if (is_string($data_string)) {
+                    Log::channel('AppflyerEvent')->info($packag_name.'af大R请求数据:' . $data_string);
+                }
+
+                $r = $this->curl($packag_name, $data_string);
+                Log::channel('AppflyerEvent')->info($packag_name.'af大R返回结果:' . $r);
+            }
+        }
+
+
+        // 当日注册当日充值的玩家的付费事件
+
+        $AccountsInfo = DB::connection('write')->table('QPAccountsDB.dbo.AccountsInfo as ai')
+            ->join('agent.dbo.order as o','ai.UserID','=','o.user_id')
+            ->where('ai.UserID',$user_id)
+            ->lock('with(nolock)')
+            ->where('pay_status',1)
+            ->whereDate('RegisterDate',date('Y-m-d'))
+//            ->whereDate('RegisterDate','2021-11-01')
+            ->count();
+
+        if ($AccountsInfo == 1) {
+            $af_purchase_Registration['appsflyer_id'] = $appsflyer_id;
+            $af_purchase_Registration['eventName'] = 'af_purchase_Registrationday';
+            $af_purchase_RegistrationData['af_revenue'] = $value;
+            $af_purchase_RegistrationData['af_currency'] = 'INR';
+            $af_purchase_Registration['eventValue'] = \GuzzleHttp\json_encode($af_purchase_RegistrationData);
+            $data_string = json_encode($af_purchase_Registration);
+            if (is_string($data_string)) {
+                Log::channel('AppflyerEvent')->info($packag_name.'当日注册当日充值的玩家的付费事件 请求数据:' . $data_string);
+            }
+
+            $r = $this->curl($packag_name, $data_string);
+            Log::channel('AppflyerEvent')->info($packag_name.'当日注册当日充值的玩家的付费事件:' . $r);
+        }
+
+
+        // 所有玩家(包括以前注册的玩家)首次充值的付费事件 af_purchase_First charge
+        $order = DB::connection('write')->table('agent.dbo.order')
+            ->where('user_id',$user_id)
+            ->where('pay_status',1)
+            ->count();
+
+        if ($order == 1) {
+            $af_purchase_First['appsflyer_id'] = $appsflyer_id;
+            $af_purchase_First['eventName'] = 'af_purchase_Firstcharge';
+            $af_purchase_FirstData['af_revenue'] = $value;
+            $af_purchase_FirstData['af_currency'] = 'INR';
+            $af_purchase_First['eventValue'] = \GuzzleHttp\json_encode($af_purchase_FirstData);
+            $data_string = json_encode($af_purchase_First);
+            if (is_string($data_string)) {
+                Log::channel('AppflyerEvent')->info($packag_name.'所有玩家(包括以前注册的玩家)首次充值的付费事件 请求数据:' . $data_string);
+            }
+
+            $r = $this->curl($packag_name, $data_string);
+            Log::channel('AppflyerEvent')->info($packag_name.'所有玩家(包括以前注册的玩家)首次充值的付费事件:' . $r);
+        }
+
+    }
+
+
+    public function curl($packag_name, $data_string,$devKey= null)
+    {
+        $url = "https://api2.appsflyer.com/inappevent/" . $packag_name;
+        $ch = curl_init($url);
+        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
+        curl_setopt($ch, CURLOPT_HEADER, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
+                'Content-Type: application/json',
+                'authentication:' . ($devKey??self::Token),
+                'Content-Length: ' . strlen($data_string))
+        );
+        curl_exec($ch);
+
+        $res = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        curl_close($ch);
+
+        return $res;
+    }
+}

+ 363 - 0
app/Http/Controllers/Admin/AccCallbackController.php

@@ -0,0 +1,363 @@
+<?php
+
+
+namespace App\Http\Controllers\Admin;
+
+
+use App\Facade\TableName;
+use App\Http\helper\NumConfig;
+use App\Models\PrivateMail;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+
+class AccCallbackController
+{
+
+    /*
+select '55'+cast(PhoneNum as varchar) as Phone,(CASE
+    WHEN CHARINDEX(' ', aw.BankUserName) > 0 THEN SUBSTRING(aw.BankUserName, 1, CHARINDEX(' ', aw.BankUserName) - 1)
+    ELSE case when aw.BankUserName is null then 'Jogador' else aw.BankUserName end
+    END)
+from QPTreasureDB.dbo.GameScoreInfo gi
+         left join QPAccountsDB.dbo.AccountsInfo af on gi.UserID = af.UserID
+         left join QPAccountsDB.dbo.AccountPhone ap on ap.UserID = gi.UserID
+         left join QPRecordDB.dbo.RecordUserTotalStatistics rt on rt.UserID = gi.UserID
+         left join QPAccountsDB.dbo.AccountWithDrawInfo as aw on aw.UserID=gi.UserID
+where af.Channel = 125
+and gi.LastLogonDate < '2023-10-15'
+and (rt.Recharge > 40)
+--    or rt.Withdraw>0 or gi.Score>2000)
+and ap.PhoneNum is not null
+-- and gi.Score>5000
+order by rt.Recharge desc;
+
+    insert  into QPRecordDB.dbo.AccountsChangeApk (UserID, FromPackage, ToPackage,  ClickTimes, State, Channel) (
+select af.UserID as UserID,'com.tiger.ourotiger' as FromPackage,'com.tiger.ourotiger2' as ToPackage,1 as ClickTimes,1 as State,125 as Channel
+    from QPTreasureDB.dbo.GameScoreInfo gi
+         left join QPAccountsDB.dbo.AccountsInfo af on gi.UserID = af.UserID
+         left join QPAccountsDB.dbo.AccountPhone ap on ap.UserID = gi.UserID
+         left join QPRecordDB.dbo.RecordUserTotalStatistics rt on rt.UserID = gi.UserID
+         left join QPAccountsDB.dbo.AccountWithDrawInfo as aw on aw.UserID=gi.UserID
+where af.Channel = 125
+and gi.LastLogonDate < '2023-10-15')
+
+    */
+
+    public function sendBonusToAll(){
+        $key="temptask_0930";
+        if(Redis::exists($key)){
+            echo "runover:".$key;
+            return;
+        }
+        $bonus=5;
+        $list=DB::table('QPRecordDB.dbo.RecordUserTotalStatistics as rs')
+            ->leftJoin("QPAccountsDB.dbo.AccountsInfo as ai", "ai.UserID", "rs.UserID")
+            ->whereRaw('rs.Recharge>=10 and ai.Channel = 125')
+            ->selectRaw("ai.UserID")
+            ->get();
+            foreach ($list as $item){
+                $this->bonusSendMail($item->UserID,$bonus);
+            }
+    }
+    // 验证码查询
+    public function make()
+    {
+//        echo $this->convertAccentsAndSpecialToNormal( 'Você acabou de receber R$8 bônus,entre no SlotsOuro para reivindicá-lo,se não conseguir entrar,exclua-o e baixe-o novamente,tudo bem.');
+//        die;
+        $key="taskCallback_013";
+        $startid=0;
+        if(Redis::exists($key)){
+            $startid=Redis::get($key);
+        }
+//        $list = DB::table("QPTreasureDB.dbo.GameScoreInfo as gi")
+//            ->leftJoin("QPAccountsDB.dbo.AccountsInfo as ai", "ai.UserID", "gi.UserID")
+//            ->leftJoin("QPAccountsDB.dbo.AccountPhone as ap", "ai.UserID", "ap.UserID")
+//            ->leftJoin("QPAccountsDB.dbo.AccountWithDrawInfo as aw", "ai.UserID", "aw.UserID")
+//            ->leftJoin("QPRecordDB.dbo.RecordUserTotalStatistics as rs", "ai.UserID", "rs.UserID")
+//            ->whereRaw("ai.UserID>$startid and rs.Recharge =0 and gi.Score > 500 and  gi.WinCount > 0 and ai.LastLogonDate < '2023-06-21' and ap.PhoneNum<>'' and ai.LastLogonDate > '2023-06-14'")
+//            ->selectRaw("ai.UserID,ai.Channel,ap.PhoneNum,rs.Recharge,rs.Withdraw,ai.Channel,gi.Score,aw.BankUserName,ai.NickName")
+//            ->orderBy('ai.UserID')
+//            ->limit(2000)
+//            ->get();
+
+        $list = DB::table("QPTreasureDB.dbo.GameScoreInfo as gi")
+            ->leftJoin("QPAccountsDB.dbo.AccountsInfo as ai", "ai.UserID", "gi.UserID")
+            ->leftJoin("QPAccountsDB.dbo.AccountPhone as ap", "ai.UserID", "ap.UserID")
+            ->leftJoin("QPAccountsDB.dbo.AccountWithDrawInfo as aw", "ai.UserID", "aw.UserID")
+            ->leftJoin("QPRecordDB.dbo.RecordUserTotalStatistics as rs", "ai.UserID", "rs.UserID")
+            ->whereRaw("ai.UserID>$startid and rs.Recharge > 20 and ai.LastLogonDate < '2023-10-27' and ap.PhoneNum<>'' and ai.LastLogonDate>'2023-10-20' and ai.Channel in (106) and ai.UserID not in (select UserID from QPRecordDB.dbo.AccountsChangeApk where AccountsChangeApk.State=2 and AccountsChangeApk.Channel=106)")
+            ->selectRaw("ai.UserID,ai.Channel,ap.PhoneNum,rs.Recharge,rs.Withdraw,ai.Channel,gi.Score,aw.BankUserName,ai.NickName")
+            ->orderBy('ai.UserID')
+            ->limit(2000)
+            ->get();
+
+        echo "<PRE>";
+        $reason = "更新失败召回";
+        $lastid=DB::table("QPRecordDB.dbo.AccountsCallbackRecords")->selectRaw("max(ID) ID")->first()->ID;
+        $lastid=$lastid??0;
+        $lastid+=100000;
+
+        $gameNames=[
+            '100'=>'Slots Ouro',
+            '101'=>'Slots Ouro',
+            '110'=>'Slots Ouro',
+            '103'=>'Slots Vencedor',
+            '113'=>'Slots Vencedor',
+            '104'=>'777 Moedas',
+            '106'=>'RIO',
+        ];
+        $storeNames=[
+            '100'=>'Play Store',
+            '101'=>'Play Store',
+            '110'=>'AppStore',
+            '103'=>'Play Store',
+            '113'=>'AppStore',
+            '104'=>'Play Store',
+            '106'=>'Play Store',
+        ];
+
+        foreach ($list as $item) {
+//            print_r($item);
+            $lastid++;
+//            $bonus
+            if($item->UserID>$startid)$startid=$item->UserID;
+
+            $bonus=3;
+            $pay=intval($item->Recharge);
+            if($pay>=50000) {
+                $bonus = 50;
+            }elseif($pay>=10000){
+                $bonus = 30;
+            }elseif($pay>=5000){
+                $bonus = 20;
+            }elseif($pay>=1000){
+                $bonus = 10;
+            }elseif($pay>=100){
+                $bonus = 8;
+            }elseif($pay>=30){
+                $bonus = 5;
+            }
+
+
+
+            $name=$item->BankUserName;
+            if(isset($name)&&$name!=""){
+                $name='Querida '.explode(" ",$name)[0].",";
+            }else if(substr($item->NickName,0,1)!='U'){
+                $name='Querida '.$item->NickName.',';
+            }else{
+                $name='';
+            }
+//            Você acabou de receber R$5 bônus, entre imediatamente em Ouro777 para reivindicá-lo, se você não conseguir entrar, exclua-o e baixe-o novamente, tudo bem.
+
+
+            $gamename=$gameNames[$item->Channel];
+            $store=$storeNames[$item->Channel];
+
+//            $msg=" {$gamename}:{$name} entre no jogo para receber seu bônus de {$bonus} moedas. Se você tiver algum problema, basta excluir o jogo e baixá-lo novamente.";
+            $code=substr($item->PhoneNum,strlen($item->PhoneNum)-6,4);
+//            $msg = "Olá, o '$gamename' foi atualizado e você pode resgatar seu bônus de $bonus reais abrindo a atualização da $store";
+//            $msg="Olá, o '$gamename' foi atualizado automaticamente, abra o jogo para resgatar seu bônus exclusivo de $bonus reais!";
+
+            $msg="{".$code."} (OTP) ganhe  R$10 bônus. Caso haja algum problema com o jogo, acesse https://rio.ouro777.com para atualizações.";
+            $msg=$this->convertAccentsAndSpecialToNormal($msg);
+
+            $bonus=$bonus*100;
+            $cashout= $item->Withdraw??0;
+            $record = [
+                'UserID' => $item->UserID,
+                'Channel' => $item->Channel,
+                'Msg' => $msg,
+                'PhoneNum' => $item->PhoneNum,
+                'Reason' => $reason,
+                'Recharge' => $pay,
+                'Profit' =>$pay -$cashout / 100,
+                'State' => 1,
+                'LeftCoin' => $item->Score,
+                'Bonus'=>$bonus,
+                'TrackToken' => $this->from10_to62($lastid),
+//'SendDate'=>$item->,
+//'Result'=>$item->,
+            ];
+            DB::connection("write")->table("QPRecordDB.dbo.AccountsCallbackRecords")->insert($record);
+//            $this->bonusSendMail($item->UserID,$bonus);
+            print_r($record);
+        }
+        Redis::set($key,$startid);
+        Redis::expire($key,3600);
+
+
+
+        return apiReturnSuc();
+    }
+
+    /**
+     * Replaces special characters in a string with their "non-special" counterpart.
+     *
+     * Useful for friendly URLs.
+     *
+     * @access public
+     * @param string
+     * @return string
+     */
+    function convertAccentsAndSpecialToNormal($string)
+    {
+        $table = array(
+            'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Ă' => 'A', 'Ā' => 'A', 'Ą' => 'A', 'Æ' => 'A', 'Ǽ' => 'A',
+            'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'ă' => 'a', 'ā' => 'a', 'ą' => 'a', 'æ' => 'a', 'ǽ' => 'a',
+
+            'Þ' => 'B', 'þ' => 'b', 'ß' => 'Ss',
+
+            'Ç' => 'C', 'Č' => 'C', 'Ć' => 'C', 'Ĉ' => 'C', 'Ċ' => 'C',
+            'ç' => 'c', 'č' => 'c', 'ć' => 'c', 'ĉ' => 'c', 'ċ' => 'c',
+
+            'Đ' => 'Dj', 'Ď' => 'D',
+            'đ' => 'dj', 'ď' => 'd',
+
+            'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ĕ' => 'E', 'Ē' => 'E', 'Ę' => 'E', 'Ė' => 'E',
+            'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ĕ' => 'e', 'ē' => 'e', 'ę' => 'e', 'ė' => 'e',
+
+            'Ĝ' => 'G', 'Ğ' => 'G', 'Ġ' => 'G', 'Ģ' => 'G',
+            'ĝ' => 'g', 'ğ' => 'g', 'ġ' => 'g', 'ģ' => 'g',
+
+            'Ĥ' => 'H', 'Ħ' => 'H',
+            'ĥ' => 'h', 'ħ' => 'h',
+
+            'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', 'İ' => 'I', 'Ĩ' => 'I', 'Ī' => 'I', 'Ĭ' => 'I', 'Į' => 'I',
+            'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'į' => 'i', 'ĩ' => 'i', 'ī' => 'i', 'ĭ' => 'i', 'ı' => 'i',
+
+            'Ĵ' => 'J',
+            'ĵ' => 'j',
+
+            'Ķ' => 'K',
+            'ķ' => 'k', 'ĸ' => 'k',
+
+            'Ĺ' => 'L', 'Ļ' => 'L', 'Ľ' => 'L', 'Ŀ' => 'L', 'Ł' => 'L',
+            'ĺ' => 'l', 'ļ' => 'l', 'ľ' => 'l', 'ŀ' => 'l', 'ł' => 'l',
+
+            'Ñ' => 'N', 'Ń' => 'N', 'Ň' => 'N', 'Ņ' => 'N', 'Ŋ' => 'N',
+            'ñ' => 'n', 'ń' => 'n', 'ň' => 'n', 'ņ' => 'n', 'ŋ' => 'n', 'ʼn' => 'n',
+
+            'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ø' => 'O', 'Ō' => 'O', 'Ŏ' => 'O', 'Ő' => 'O', 'Œ' => 'O',
+            'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ø' => 'o', 'ō' => 'o', 'ŏ' => 'o', 'ő' => 'o', 'œ' => 'o', 'ð' => 'o',
+
+            'Ŕ' => 'R', 'Ř' => 'R',
+            'ŕ' => 'r', 'ř' => 'r', 'ŗ' => 'r',
+
+            'Š' => 'S', 'Ŝ' => 'S', 'Ś' => 'S', 'Ş' => 'S',
+            'š' => 's', 'ŝ' => 's', 'ś' => 's', 'ş' => 's',
+
+            'Ŧ' => 'T', 'Ţ' => 'T', 'Ť' => 'T',
+            'ŧ' => 't', 'ţ' => 't', 'ť' => 't',
+
+            'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ũ' => 'U', 'Ū' => 'U', 'Ŭ' => 'U', 'Ů' => 'U', 'Ű' => 'U', 'Ų' => 'U',
+            'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ũ' => 'u', 'ū' => 'u', 'ŭ' => 'u', 'ů' => 'u', 'ű' => 'u', 'ų' => 'u',
+
+            'Ŵ' => 'W', 'Ẁ' => 'W', 'Ẃ' => 'W', 'Ẅ' => 'W',
+            'ŵ' => 'w', 'ẁ' => 'w', 'ẃ' => 'w', 'ẅ' => 'w',
+
+            'Ý' => 'Y', 'Ÿ' => 'Y', 'Ŷ' => 'Y',
+            'ý' => 'y', 'ÿ' => 'y', 'ŷ' => 'y',
+
+            'Ž' => 'Z', 'Ź' => 'Z', 'Ż' => 'Z',
+            'ž' => 'z', 'ź' => 'z', 'ż' => 'z',
+
+            '“' => '"', '”' => '"', '‘' => "'", '’' => "'", '•' => '-', '…' => '...', '—' => '-', '–' => '-', '¿' => '?', '¡' => '!', '°' => ' degrees ',
+            '¼' => ' 1/4 ', '½' => ' 1/2 ', '¾' => ' 3/4 ', '⅓' => ' 1/3 ', '⅔' => ' 2/3 ', '⅛' => ' 1/8 ', '⅜' => ' 3/8 ', '⅝' => ' 5/8 ', '⅞' => ' 7/8 ',
+            '÷' => ' divided by ', '×' => ' times ', '±' => ' plus-minus ', '√' => ' square root ', '∞' => ' infinity ',
+            '≈' => ' almost equal to ', '≠' => ' not equal to ', '≡' => ' identical to ', '≤' => ' less than or equal to ', '≥' => ' greater than or equal to ',
+            '←' => ' left ', '→' => ' right ', '↑' => ' up ', '↓' => ' down ', '↔' => ' left and right ', '↕' => ' up and down ',
+            '℅' => ' care of ', '℮' => ' estimated ',
+            'Ω' => ' ohm ',
+            '♀' => ' female ', '♂' => ' male ',
+            '©' => ' Copyright ', '®' => ' Registered ', '™' => ' Trademark ',
+        );
+
+        $string = strtr($string, $table);
+        // Currency symbols: £¤¥€  - we dont bother with them for now
+        $string = preg_replace("/[^\x9\xA\xD\x20-\x7F]/u", "", $string);
+
+        return $string;
+    }
+    public static function bonusSendMail($UserID,  $bonus)
+    {
+        $amount = $bonus;
+        $TitleString = 'Por favor, reivindique seu bônus';
+        $TextString = "Desejo-lhe sempre boa sorte e ganhe altos multiplicadores!";
+        PrivateMail::sendMail(2, $UserID, $TitleString, $TextString, '30000,'.$amount, '', $amount, 3);
+    }
+    /**
+     * 获取该条记录的自增ID
+     * 将自增转换为62进制,并拼接网址 如:http://qetee.com/w7e
+     * 用户访问到 http://qetee.com/w7e 时,提取短网址后缀 w7e
+     * 将短网址后缀转换为10进制,得到自增ID号 如:123456
+     * 使用查询该记录,进行业务逻辑处理(比如跳转)
+     */
+
+    /**
+     * 十进制数转换成62进制
+     *
+     * @param integer $num
+     * @return string
+     */
+    function from10_to62($num) {
+        $to = 62;
+        $dict = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+        $ret = '';
+        do {
+            $ret = $dict[bcmod($num, $to)] . $ret;
+            $num = bcdiv($num, $to);
+        } while ($num > 0);
+        return $ret;
+    }
+
+    /**
+     * 62进制数转换成十进制数
+     *
+     * @param string $num
+     * @return string
+     */
+    function from62_to10($num) {
+        $from = 62;
+        $num = strval($num);
+        $dict = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+        $len = strlen($num);
+        $dec = 0;
+        for($i = 0; $i < $len; $i++) {
+            $pos = strpos($dict, $num[$i]);
+            $dec = bcadd(bcmul(bcpow($from, $len - $i - 1), $pos), $dec);
+        }
+        return $dec;
+    }
+    // 绑定手机号 -- 手动
+    public function bind_phone(Request $request, $UserID)
+    {
+        if ($request->isMethod('post')) {
+
+            $post = $request->post();
+
+            $Channel = DB::connection('write')->table(TableName::QPAccountsDB() . 'AccountsInfo')
+                ->where('UserID', $UserID)->select('Channel')->first()->Channel;
+
+            $IsUserBindPhone = DB::table(TableName::QPAccountsDB() . 'AccountPhone')
+                ->where('UserID', $UserID)
+                ->first();
+            if ($IsUserBindPhone) {
+                return apiReturnFail('用户已绑定');
+            }
+            DB::table(TableName::QPAccountsDB() . 'AccountPhone')
+                ->insert([
+                    'PhoneNum' => (int)$post['PhoneNum'],
+                    'BindDate' => now(),
+                    'LogonPass' => $post['LogonPass'],
+                    'UserID' => $UserID,
+                    'Channel' => $Channel,
+                ]);
+
+            return apiReturnSuc();
+        } else {
+            return view('admin.code.bind_phone', compact('UserID'));
+        }
+    }
+}

+ 63 - 0
app/Http/Controllers/Admin/AdminLogController.php

@@ -0,0 +1,63 @@
+<?php
+
+
+namespace App\Http\Controllers\Admin;
+
+
+use App\AdminUser;
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+class AdminLogController extends BaseController
+{
+    public function index(Request $request)
+    {
+
+        $start_time = $request->get('start_time');
+        $end_time = $request->get('end_time');
+        $nickname = $request->get('nickname');
+
+        $where = [];
+
+        !empty($start_time) && $where[] = ['log.create_at','>=',$start_time];
+        !empty($end_time) && $where[] = ['log.create_at','<=',$end_time];
+        !empty($nickname) && $where[] = ['admin.nickname','=',$nickname];
+
+
+        $list = DB::table('agent.dbo.admin_log as log')
+            ->join('agent.dbo.admin_users as admin','log.admin_id','=','admin.id')
+            ->where($where)
+            ->orderByDesc(DB::raw("CONVERT(varchar(100), log.create_at, 120)"))
+            ->select('log.id','log.admin_id','url','content','explain',DB::raw("CONVERT(varchar(100), log.create_at, 120) as create_at"),DB::raw("CONVERT(varchar(100), log.update_at, 120) as update_at"))
+
+            ->groupBy('log.id','log.admin_id','url','content','explain',DB::raw("CONVERT(varchar(100), log.create_at, 120)"),DB::raw("CONVERT(varchar(100), log.update_at, 120)"))
+
+            ->paginate(10);
+        foreach ($list as &$value){
+            $admin = AdminUser::where('id',$value->admin_id)->first();
+            $value->nickname = $admin->nickname;
+            $value->account = $admin->account;
+            $value->roles = $admin->roles->map(function ($val){
+                    return $val->name;
+                })[0] ?? '';
+        }
+
+        return  view('admin.adminLog.record',[
+            'list'=>$list,
+            'start_time' => $start_time,
+            'end_time' => $end_time,
+            'nickname' => $nickname
+        ]);
+        return view('admin.adminLog.index',['list'=>$list]);
+    }
+
+    public function show($id)
+    {
+
+        $list = DB::table('agent.dbo.admin_log as log')
+            ->select('explain')
+            ->first()->explain;
+        return view('admin.adminLog.show',['list'=>$list]);
+    }
+}

+ 576 - 0
app/Http/Controllers/Admin/AdministratorController.php

@@ -0,0 +1,576 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * Author: woann <304550409@qq.com>
+ * Date: 18-10-26下午1:23
+ * Desc: 管理员
+ */
+
+namespace App\Http\Controllers\Admin;
+
+use App\AdminMenu;
+use App\AdminPermission;
+use App\AdminRole;
+use App\AdminUser;
+use App\Http\Controllers\Controller;
+use App\Http\helper\Helper;
+use App\Utility\Rbac;
+use Illuminate\Http\Request;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+
+class AdministratorController extends Controller
+{
+    /**
+     * @Desc: 菜单列表
+     * @Author: woann <304550409@qq.com>
+     * @return \Illuminate\View\View
+     */
+    public function menuList()
+    {
+        // 获取一级菜单
+        return view('admin.menu', ['list' => AdminMenu::where('pid', 0)->get()]);
+    }
+
+    /**
+     * @Desc: 添加菜单
+     * @Author: woann <304550409@qq.com>
+     * @param Request $request
+     * @return \Illuminate\View\View
+     */
+    public function menuAddView(Request $request)
+    {
+        $roles = AdminRole::get();
+        $topMenu = AdminMenu::where('pid', 0)->get();
+        return view('admin.menu_add', ['roles' => $roles, 'top_menu' => $topMenu]);
+    }
+
+    public function menuAdd(Request $request)
+    {
+        $data = $request->except(['role', 's']);
+        $roles = new Collection($request->input('roles'));
+        if ($roles->isEmpty()) {
+            return $this->json(500, '未选择任何角色');
+        }
+        $menu = new AdminMenu();
+        $menu->fill($data);
+        $menu->save();
+        // 保存菜单所属角色
+        $roles->map(function ($roleId) use ($menu) {
+            $role = AdminRole::find($roleId);
+            $menu->roles()->attach($role);
+        });
+        return $this->json(200, '添加成功');
+    }
+
+    /**
+     * @Desc: 修改菜单
+     * @Author: woann <304550409@qq.com>
+     * @param Request $request
+     * @param $id
+     * @return \Illuminate\View\View
+     */
+    public function menuUpdateView(Request $request, $id)
+    {
+        $roles = AdminRole::get();
+        $menu = AdminMenu::findOrFail($id);
+        $roles->map(function ($role) use ($menu) {
+            $menu->roles->each(function ($mRole) use (&$role) {
+                if ($mRole->id === $role->id) {
+                    $role->checked = true;
+                }
+            });
+            return $role;
+        });
+        $topMenu = AdminMenu::where('pid', 0)->get();
+        return view('admin.menu_update', [
+            'roles' => $roles,
+            'top_menu' => $topMenu,
+            'menu' => $menu,
+        ]);
+    }
+
+    public function menuUpdate(Request $request, $id)
+    {
+        $menu = AdminMenu::findOrFail($id);
+        $roles = new Collection($request->input('roles'));
+        if ($roles->isEmpty()) {
+            return $this->json(500, '未选择任何角色');
+        }
+        // 基础信息更新
+        $data = $request->except(['role', 's']);
+        $menu->fill($data)->save();
+        // 删除原有关联数据
+        $menu->roles()->detach();
+        // 重新关联数据
+        $roles->each(function ($roleId) use ($menu) {
+            $role = AdminRole::find($roleId);
+            $menu->roles()->attach($role);
+        });
+        return $this->json(200, '修改成功');
+    }
+
+    /**
+     * @Desc: 删除菜单
+     * @Author: woann <304550409@qq.com>
+     * @param $id
+     * @return mixed
+     */
+    public function menuDel($id)
+    {
+        $menu = AdminMenu::findOrFail($id);
+        $menu->roles()->detach();
+        $menu->delete();
+        return $this->json(200, '删除成功');
+    }
+
+    public function roleList()
+    {
+        return view('admin.role', [
+            'list' => AdminRole::paginate(10),
+        ]);
+    }
+
+    /**
+     * @Desc: 添加角色
+     * @Author: woann <304550409@qq.com>
+     * @param Request $request
+     * @return \Illuminate\View\View
+     */
+    public function roleAddView(Request $request)
+    {
+        return view('admin.role_add', [
+            'permissions' => AdminPermission::get(),
+        ]);
+    }
+
+    public function roleAdd(Request $request)
+    {
+        $param = $request->post();
+        $role = new AdminRole();
+        $role->fill($param);
+        $role->save();
+        if (isset($param['permissions'])) {
+            (new Collection($param['permissions']))->map(function ($permissionId) use ($role) {
+                $permission = AdminPermission::find($permissionId);
+                $role->permissions()->attach($permission);
+            });
+        }
+        return $this->json(200, "添加成功");
+    }
+
+    /**
+     * @Desc: 修改角色
+     * @Author: woann <304550409@qq.com>
+     * @param Request $request
+     * @param $id
+     * @return \Illuminate\View\View
+     */
+    public function roleUpdateView(Request $request, $id)
+    {
+        $role = AdminRole::findOrFail($id);
+        $permissions = AdminPermission::get();
+        $permissions->map(function ($permission) use ($role) {
+            $permission->checked = false;
+            $role->permissions->each(function ($rPermission) use ($role, &$permission) {
+                if ($rPermission->id === $permission->id) {
+                    $permission->checked = true;
+                    return false;
+                }
+            });
+            return $permission;
+        });
+        return view('admin.role_update', ['role' => $role, 'permissions' => $permissions]);
+    }
+
+    public function roleUpdate(Request $request, $id)
+    {
+        $param = $request->post();
+        $role = AdminRole::findOrFail($id);
+        $role->fill($param);
+        $role->save();
+        // 删除所有权限关联
+        $role->permissions()->detach();
+        // 录入权限关联
+        if (isset($param['permissions'])) {
+            (new Collection($param['permissions']))->map(function ($permissionId) use ($role) {
+                $permission = AdminPermission::find($permissionId);
+                $role->permissions()->attach($permission);
+            });
+        }
+        return $this->json(200, "修改成功");
+
+    }
+
+    /**
+     * @Desc: 删除角色
+     * @Author: woann <304550409@qq.com>
+     * @param $id
+     * @return mixed
+     */
+    public function roleDel($id)
+    {
+        if ($id == 1) {
+            return $this->json(500, '超级管理员不可删除');
+        }
+        $role = AdminRole::findOrFail($id);
+        // 删除所有多对多关系
+        $role->users()->detach();
+        $role->menus()->detach();
+        $role->permissions()->detach();
+        $role->delete();
+        return $this->json(200, '删除成功');
+    }
+
+    /**
+     * @return mixed
+     * 权限列表
+     */
+    public function permissionList()
+    {
+        return view('admin.permission', [
+            'list' => AdminPermission::get(),
+        ]);
+    }
+
+    /**
+     * @param Request $request
+     * @return mixed
+     * 添加权限
+     */
+    public function permissionAddView(Request $request)
+    {
+        //渲染页面
+        $routes = Rbac::getAllRoutes();
+        // foreach ($routes as $key => $value) {
+        //     print_r(json_decode(json_encode($value),true));
+        // }
+        return view('admin.permission_add', ['routes' => $routes]);
+        // return view('admin.permission_add', ['routes' => AdminMenu::orderBy('id')->get()]);
+    }
+
+    public function permissionAdd(Request $request)
+    {
+        $data = $request->post();
+        $permission = new AdminPermission();
+        $permission->fill($data);
+        $permission->save();
+        return $this->json(200, '添加成功');
+    }
+
+    /**
+     * @param Request $request
+     * @param $id
+     * @return mixed
+     * 修改权限
+     */
+    public function permissionUpdateView(Request $request, $id)
+    {
+        $permission = AdminPermission::findOrFail($id);
+        $rbacRoutes = Rbac::getAllRoutes();
+        $checkRoutes = $permission->routes->map(function ($route) {
+            $routeObj = new \StdClass();
+            $routeObj->rbacRule = $route;
+            return $routeObj;
+        });
+        $uncheckRoutes = new Collection();
+        $rbacRoutes->each(function ($route) use ($permission, $checkRoutes, &$uncheckRoutes) {
+            $uncheckFlag = true;
+            $checkRoutes->each(function ($checkRoute) use ($route, &$uncheckFlag) {
+                if ($route->rbacRule === $checkRoute->rbacRule) {
+                    $uncheckFlag = false;
+                }
+            });
+            if ($uncheckFlag) {
+                $uncheckRoutes->push($route);
+            }
+        });
+        return view('admin.permission_update', [
+            'permission' => $permission,
+            'uncheck_routes' => $uncheckRoutes,
+            'check_routes' => $checkRoutes,
+        ]);
+    }
+
+    public function permissionUpdate(Request $request, $id)
+    {
+        $data = $request->post();
+        $permission = AdminPermission::findOrFail($id);
+        $permission->fill($data);
+        $permission->save();
+        return $this->json(200, '修改成功');
+
+    }
+
+    /**
+     * @return mixed
+     * 删除权限
+     */
+    public function permissionDel($id)
+    {
+        $permission = AdminPermission::findOrFail($id);
+        // 解除所有多对多关系
+        $permission->roles()->detach();
+        $permission->delete();
+        return $this->json(200, '删除成功');
+    }
+
+    /**
+     * @return mixed
+     * 管理员列表
+     */
+    public function administratorList()
+    {
+//        $admin_user = AdminUser::pluck('account','id')->toArray();
+
+        $admins = AdminUser::paginate(10);
+        $admin_ids = [];
+        foreach ($admins as &$val) {
+            $admin_ids[] = $val->id;
+        }
+        $history_lottery_amount = DB::table('agent.dbo.admin_score')->where(['type' => 1])->whereIn('admin_id', $admin_ids)->selectRaw('IsNull(sum(change_score),0)as change_score,admin_id')->groupBy('admin_id')->pluck('change_score', 'admin_id')->toArray();
+        $history_recharge_amount = DB::table('agent.dbo.admin_score')->where(['type' => 2])->whereIn('admin_id', $admin_ids)->selectRaw('IsNull(sum(change_score),0)as change_score,admin_id')->groupBy('admin_id')->pluck('change_score', 'admin_id')->toArray();
+
+
+        foreach ($admins as &$val) {
+
+            $val->history_lottery_amount = isset($history_lottery_amount[$val->id]) ? $history_lottery_amount[$val->id] : '';
+            $val->history_recharge_amount = isset($history_recharge_amount[$val->id]) ? $history_recharge_amount[$val->id] : '';
+        }
+        return view('admin.administrator', [
+            'admins' => $admins,
+        ]);
+    }
+
+    /**
+     * @param Request $request
+     * @return mixed
+     * 添加管理员
+     */
+    public function administratorAddView(Request $request)
+    {
+        $roles = AdminRole::select('id', 'name')->get();
+
+        $channels = DB::table('QPPlatformDB.dbo.ChannelPackageName')
+                      ->pluck('Channel', 'Channel');
+
+        $types = [1 => '管理后台', 2 => '渠道后台', 3 => '数据统计新后台'];
+        return view('admin.administrator_add', ['roles' => $roles, 'types' => $types,'channels'=>$channels]);
+    }
+
+    public function administratorAdd(Request $request)
+    {
+        $post = $request->post();
+
+        $post['channel'] = json_encode($post['channel']);
+
+        $roles = (new Collection($request->post('roles')));
+        if (AdminUser::isExist($post['account'], $post['type'])) {
+            return $this->json(500, '该账号已存在');
+        }
+        $admin = new AdminUser();
+        $admin->fill($post);
+        $admin->save();
+        $roles->map(function ($roleId) use ($admin) {
+            $role = AdminRole::find($roleId);
+            $admin->roles()->attach($role);
+        });
+        return $this->json(200, '添加成功');
+    }
+
+    public function administratorUpdateView(Request $request, $id)
+    {
+        $roles = AdminRole::select('id', 'name')->get();
+        $admin = AdminUser::findOrFail($id);
+        $selectRoleIdArr = [];
+        $admin->roles->map(function ($role) use (&$selectRoleIdArr) {
+            $selectRoleIdArr[] = $role->id;
+        });
+        $admin->channel = json_decode($admin->channel,true);
+
+        $channels = DB::table('QPPlatformDB.dbo.ChannelPackageName')
+                      ->pluck('Channel', 'Channel');
+
+
+        return view('admin.administrator_update', [
+            'admin' => $admin,
+            'roles' => $roles,
+            'channels'=>$channels,
+            's_role_id_arr' => $selectRoleIdArr,
+
+        ]);
+    }
+
+    public function administratorUpdate(Request $request, $id)
+    {
+        $post = $request->post();
+        $roles = (new Collection($request->post('roles')));
+        $admin = AdminUser::findOrFail($id);
+        if ($admin->isExistForUpdate($post['account'], $post['type'])) {
+            return $this->json(500, '该账号已存在');
+        }
+        $post['channel'] = json_encode($post['channel']);
+        $post = array_filter($post);
+
+        $admin->fill($post)->save();
+
+
+        // 删除用户的所有关联角色
+        $admin->roles()->detach();
+        $roles->map(function ($roleId) use ($admin) {
+            $role = AdminRole::find($roleId);
+            $admin->roles()->attach($role);
+        });
+        return $this->json(200, '修改成功');
+
+    }
+
+    /**
+     * @return mixed
+     * 删除管理员
+     */
+    public function administratorDel($id)
+    {
+        $admin = AdminUser::findOrFail($id);
+        // 解除管理员角色多对多关系
+        $admin->roles()->detach();
+        $admin->delete();
+        return $this->json(200, '删除成功');
+    }
+
+    public function administratorBlock($id)
+    {
+        $admin = AdminUser::where('id', $id)->value('status');
+        if ($admin == 1) {
+            AdminUser::where('id', $id)->update(['status' => -1]);
+            return apiReturnSuc('success', '禁用成功');
+        } else {
+            AdminUser::where('id', $id)->update(['status' => 1]);
+            return apiReturnSuc('success', '启用成功');
+        }
+    }
+
+
+    /**
+     * @param Request $request
+     * @return mixed
+     * 后台登录
+     */
+    public function login(Request $request)
+    {
+        if(!isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))return '';//$_SERVER['HTTP_ACCEPT_LANGUAGE']="zh_CN";
+        $lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 5); //只取前4位,这样只判断最优先的语言。如果取前5位,可能出现en,zh的情况,影响判断。
+        if (preg_match("/zh/i", $lang)){
+            \App::setLocale("zh_CN");
+        }else{
+            \App::setLocale("en_US");
+        }
+        return view('admin.login');
+    }
+
+    public function checkLogin(Request $request)
+    {
+        $lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 5); //只取前4位,这样只判断最优先的语言。如果取前5位,可能出现en,zh的情况,影响判断。
+        if (preg_match("/zh/i", $lang)){
+            \App::setLocale("zh_CN");
+        }else{
+            \App::setLocale("en_US");
+        }
+
+        $post = $request->post();
+        if (empty($post['account'])) {
+            return $this->json(500, trans('cs.login.notice_user'));
+        }
+        if (empty($post['password'])) {
+            return $this->json(500, trans('cs.login.notice_pass'));
+        }
+
+        $admin = AdminUser::where('account', $post['account'])->first();
+
+        if (empty($admin) || $admin->type != 1) {
+            return $this->json(500, trans('cs.login.cannotfinduser'));
+        }
+        if (!password_verify($post['password'], $admin->password)) {
+            return $this->json(500, trans('cs.login.wrongpass'));
+        }
+        if ($admin->status == -1) {
+            return $this->json(500, trans('cs.login.block'));
+        }
+
+        $roles = $admin->roles;
+
+        $ip = $request->ip();
+
+
+        // 超管不验证IP白名单
+        $whiteListId = [];//[1, 12, 2010];
+        foreach ($roles as $role) {
+            if (in_array($role->id, $whiteListId)) {
+                $white_ip = DB::table('agent.dbo.ip_white_list')->where('ip', $ip)->first();
+                if (!$white_ip) {
+//                    return $this->json(500, '请联系管理员添加IP白名单!'.$ip);
+                }
+            }
+        }
+//
+//
+//        // 添加ip登录管理
+//        $ip_data = [
+//            'admin_id' => $admin->id,
+//            'ip' => $ip,
+//            'ip_address' => Helper::get_ip_city($ip),
+//            'last_login_time' => date('Y-m-d H:i:s')
+//        ];
+
+//        $_where = [
+//            'admin_id' => $admin->id,
+//            'ip' => $ip
+//        ];
+//        $query = DB::table('agent.dbo.admin_login_ip')->where($_where)->first();
+//        if (!$query) {
+//        DB::table('agent.dbo.admin_login_ip')->insert($ip_data);
+//        }
+
+
+        $request->session()->put('admin', $admin);
+        return $this->json(200, trans('cs.login.notice'));
+
+    }
+
+    /**
+     * @param Request $request
+     * @param $id
+     * @return mixed
+     * 修改信息
+     */
+    public function editInfoView(Request $request, $id)
+    {
+        return view('admin.edit_info', ['admin' => AdminUser::findOrFail($id)]);
+    }
+
+    public function editInfo(Request $request, $id)
+    {
+        $post = $request->post();
+        $admin = AdminUser::findOrFail($id);
+        $admin->fill($post);
+        $admin->save();
+        $request->session()->put('admin', $admin);
+        return $this->json(200, '修改成功');
+    }
+
+    /**
+     * @param Request $request
+     * @return mixed
+     * 退出登录
+     */
+    public function logout(Request $request)
+    {
+        $admin_id=$request->session()->get("admin")->id;
+        $adminKey="adminuser_$admin_id";
+        Redis::del($adminKey);
+        $request->session()->flush();
+        return redirect('/admin/login_op');
+    }
+
+}

Деякі файли не було показано, через те що забагато файлів було змінено