seven 5 dní pred
rodič
commit
717b51f178
100 zmenil súbory, kde vykonal 11308 pridanie a 7 odobranie
  1. 21 5
      .gitignore
  2. 3 0
      INSTALL.md
  3. 160 2
      README.md
  4. 6 0
      api.txt
  5. 19 0
      apidoc.json
  6. 32 0
      app/Console/Kernel.php
  7. 123 0
      app/Constants/Captcha.php
  8. 50 0
      app/Constants/HttpStatus.php
  9. 29 0
      app/Constants/StepStatus.php
  10. 65 0
      app/Constants/Util.php
  11. 50 0
      app/Exceptions/Handler.php
  12. 10 0
      app/Exceptions/MessageException.php
  13. 8 0
      app/Exceptions/ValidationFailedException.php
  14. 46 0
      app/Helpers/Sign.php
  15. 1127 0
      app/Helpers/TronHelper.php
  16. 135 0
      app/Helpers/TronRawSerializer.php
  17. 189 0
      app/Helpers/helpers.php
  18. 113 0
      app/Http/Controllers/Controller.php
  19. 291 0
      app/Http/Controllers/admin/Admin.php
  20. 58 0
      app/Http/Controllers/admin/Balance.php
  21. 368 0
      app/Http/Controllers/admin/Config.php
  22. 128 0
      app/Http/Controllers/admin/Game.php
  23. 232 0
      app/Http/Controllers/admin/Menu.php
  24. 146 0
      app/Http/Controllers/admin/Role.php
  25. 216 0
      app/Http/Controllers/admin/Room.php
  26. 31 0
      app/Http/Controllers/admin/Sync.php
  27. 114 0
      app/Http/Controllers/admin/Upload.php
  28. 97 0
      app/Http/Controllers/admin/User.php
  29. 250 0
      app/Http/Controllers/admin/Wallet.php
  30. 149 0
      app/Http/Controllers/admin/Withdraw.php
  31. 139 0
      app/Http/Controllers/api/Home.php
  32. 736 0
      app/Http/Controllers/api/TelegramWebHook.php
  33. 82 0
      app/Http/Kernel.php
  34. 21 0
      app/Http/Middleware/Authenticate.php
  35. 59 0
      app/Http/Middleware/CheckButtonPermission.php
  36. 17 0
      app/Http/Middleware/EncryptCookies.php
  37. 46 0
      app/Http/Middleware/JwtAdminMiddleware.php
  38. 58 0
      app/Http/Middleware/JwtMiddleware.php
  39. 17 0
      app/Http/Middleware/PreventRequestsDuringMaintenance.php
  40. 32 0
      app/Http/Middleware/RedirectIfAuthenticated.php
  41. 19 0
      app/Http/Middleware/TrimStrings.php
  42. 20 0
      app/Http/Middleware/TrustHosts.php
  43. 28 0
      app/Http/Middleware/TrustProxies.php
  44. 22 0
      app/Http/Middleware/ValidateSignature.php
  45. 17 0
      app/Http/Middleware/VerifyCsrfToken.php
  46. 27 0
      app/Mail/CodeMail.php
  47. 27 0
      app/Mail/ForgotMail.php
  48. 32 0
      app/Models/Address.php
  49. 49 0
      app/Models/Admin.php
  50. 31 0
      app/Models/BalanceLog.php
  51. 32 0
      app/Models/Chat.php
  52. 35 0
      app/Models/Coin.php
  53. 59 0
      app/Models/Collect.php
  54. 26 0
      app/Models/Config.php
  55. 32 0
      app/Models/Game.php
  56. 48 0
      app/Models/Menu.php
  57. 32 0
      app/Models/Message.php
  58. 32 0
      app/Models/Permission.php
  59. 33 0
      app/Models/PermissionUser.php
  60. 62 0
      app/Models/Recharge.php
  61. 54 0
      app/Models/Role.php
  62. 32 0
      app/Models/RoleMenu.php
  63. 33 0
      app/Models/RoleUser.php
  64. 32 0
      app/Models/Room.php
  65. 53 0
      app/Models/RoomUser.php
  66. 39 0
      app/Models/User.php
  67. 32 0
      app/Models/UserGame.php
  68. 35 0
      app/Models/Wallet.php
  69. 32 0
      app/Models/Withdraw.php
  70. 55 0
      app/Providers/AppServiceProvider.php
  71. 30 0
      app/Providers/AuthServiceProvider.php
  72. 21 0
      app/Providers/BroadcastServiceProvider.php
  73. 42 0
      app/Providers/EventServiceProvider.php
  74. 52 0
      app/Providers/RouteServiceProvider.php
  75. 151 0
      app/Services/AdminService.php
  76. 114 0
      app/Services/BalanceLogService.php
  77. 116 0
      app/Services/BaseService.php
  78. 41 0
      app/Services/ChannelService.php
  79. 91 0
      app/Services/CoinService.php
  80. 190 0
      app/Services/CollectService.php
  81. 83 0
      app/Services/GameService.php
  82. 52 0
      app/Services/JwtService.php
  83. 211 0
      app/Services/MenuService.php
  84. 194 0
      app/Services/OnLineService.php
  85. 186 0
      app/Services/RechargeService.php
  86. 64 0
      app/Services/RecordService.php
  87. 145 0
      app/Services/RoleMenuService.php
  88. 147 0
      app/Services/RoleService.php
  89. 145 0
      app/Services/RoleUserService.php
  90. 1007 0
      app/Services/RoomService.php
  91. 252 0
      app/Services/SettlementService.php
  92. 261 0
      app/Services/TopUpService.php
  93. 129 0
      app/Services/TutorialService.php
  94. 108 0
      app/Services/UserService.php
  95. 231 0
      app/Services/WalletService.php
  96. 517 0
      app/Services/WithdrawService.php
  97. 53 0
      artisan
  98. 55 0
      bootstrap/app.php
  99. 2 0
      bootstrap/cache/.gitignore
  100. 85 0
      composer.json

+ 21 - 5
.gitignore

@@ -1,6 +1,22 @@
-# ---> Laravel
-/bootstrap/compiled.php
-.env.*.php
-.env.php
+/node_modules
+/public/build
+/public/hot
+/public/.htaccess
+/public/storage
+/public/apidoc/
+/storage/*.key
+/storage
+/vendor
 .env
-
+.env.backup
+.env.production
+.phpunit.result.cache
+Homestead.json
+Homestead.yaml
+auth.json
+npm-debug.log
+yarn-error.log
+/.fleet
+/.idea
+/.vscode
+composer.lock

+ 3 - 0
INSTALL.md

@@ -0,0 +1,3 @@
+# 扩展
+    - php_gmp
+        - composer require kornrunner/secp256k1 使用

+ 160 - 2
README.md

@@ -1,3 +1,161 @@
-# bot-28
+# 项目名称
+项目介绍
+
+## 目录
+- [安装指南](#一、安装指南) 
+    - [克隆项目](#克隆项目) 
+    - [准备工作](#准备工作)
+    - [ffmpeg安装](#ffmpeg安装) 
+    - [安装依赖](#安装依赖)
+    - [配置](#配置)
+        - [运行目录](#运行目录)
+        - [apache](#apache)
+        - [nginx](#nginx)
+        - [.env](#env)
+- [数据库](#二、数据库)
+- [网站初始信息](#三、初始信息)
+
+
+## 一、安装指南
+### 克隆项目
+```bash
+git clone http://47.76.126.2:3000/tg-bot/tg-bot-api.git
+```
+### 准备工作
+需要先安装好运行环境,推荐使用宝塔服务器,安装LNMP的架构,建议使用nginx作为服务器,不建议使用apache。需要安装以下软件:
+
+|  所需环境 | 版本 | 备注 | 推荐版本 |
+|---------|----|----|---|
+| linux    | \>= 7.0 |     | 7.9 |
+| nginx    | \>= 1.17 |     | 最新的 |
+| php | \>= 8.0.2 |   8.0.2 ~ 8.2   | 8.2.9 |
+| mysql    | \>= 5.7 | 必须要5.7及以上     | 5.7 |
+| node     | >= 22 
+### 安装依赖
+```bash
+# 安装依赖
+composer install
+
+# 创建符号链接
+php artisan storage:link
+```
+### 配置
+- #### 运行目录:
+    - 部署完成后,请修改 【运行目录】 为 `public` ,这里以宝塔为例:
+    ![这是图片](public/static/img/1.png)
+
+- #### apache:
+```apache
+<IfModule mod_rewrite.c>
+        Options +FollowSymlinks -Multiviews
+        RewriteEngine on
+        RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+        RewriteCond %{REQUEST_FILENAME} !-d
+        RewriteCond %{REQUEST_FILENAME} !-f
+        RewriteRule ^(.*)$ index.php?s=$1 [QSA,PT,L]
+</IfModule>
+
+# 可根据实际情况进行修改
+<IfModule mod_headers.c>
+        Header set Accept-Ranges bytes
+        Header set Access-Control-Allow-Origin "*"
+        Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
+        Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
+        Header set Access-Control-Allow-Credentials "true"
+</IfModule>
+```
+- #### nginx:
+```nginx
+location / {
+        add_header 'Access-Control-Allow-Origin' '*';
+        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
+        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
+        add_header 'Access-Control-Allow-Credentials' 'true';
+        add_header 'Access-Control-Max-Age' 3600;
+
+        if ($request_method = 'OPTIONS') {
+            # OPTIONS 请求直接返回 200
+            return 204;
+        } 
+
+        #try_files $uri $uri/ /index.php?$query_string;        
+        if (!-e $request_filename) {
+            rewrite ^/index.php(.*)$ /index.php?s=$1 last;
+            rewrite ^(.*)$ /index.php?s=$1 last;
+            break;
+        }
+}
+```
+- #### .env:
+    (1) 将 `example.env` 重命名为 `.env`
+    
+    (2) 修改以下配置项
+```bash
+# 以下配置仅为示例
+
+# App
+APP_NAME=lovingtalk
+APP_DEBUG=false
+APP_URL=http://localhost:8080
+
+# 数据库
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=bot
+DB_USERNAME=root
+DB_PASSWORD=123456
+DB_PREFIX=bot_
+
+# 电子邮件
+MAIL_MAILER=smtp
+MAIL_ENCRYPTION=tls
+MAIL_PORT=587
+MAIL_FROM_NAME="${APP_NAME}"
+MAIL_HOST=
+MAIL_FROM_ADDRESS=""
+MAIL_USERNAME=
+MAIL_PASSWORD=
+MAIL_EXP=600  # 邮件验证码有效期(秒)
+```
+
+
+## 二、数据库
+<!-- 首次部署请依次导入以下文件,增量更新请根据实际情况导入即可。-->
+- 请依次导入以下文件
+
+|序号|文件|说明|
+|---|---|---|
+|1|[/sql/sql_prod.sql](/sql/sql_prod.sql)|初始数据|
+
+## 三、初始信息
+- 通过部署的域名访问程序后台系统,登录后请及时修改密码!!!
+- 初始账号密码: 
+    - 用户名:`admin`
+    - 密码:`123456`
+`注意:登录后请及时修改管理员密码,防止泄露。`
+
+## 四、设置机器人回调地址、菜单等
+- .env设置机器人信息
+- 访问 https://域名/api/setWebHook
+
+## 五、设置区块链信息
+- .env 
+    - 设置归集USDT地址
+    - 设置TRX能量的秘钥
+    - 设置是区块网络
+
+## 六、设置定时任务
+- 30秒运行一次
+    - 充值确认:https://域名/admin/sync/recharge
+    - 余额归集:https://域名/admin/sync/collect
+    - 结算通知:https://域名/admin/sync/settle
+
+    
+
+
+
+
+
+
 
-机器人PC28

+ 6 - 0
api.txt

@@ -0,0 +1,6 @@
+apidoc -i ./app/Http/Controllers/api/ -o ./public/doc/ -t ./public/apidoc/template/
+
+
+
+
+apidoc -i ./app/Http/Controllers/admin/ -o ./public/doc/ -t ./public/apidoc/template/

+ 19 - 0
apidoc.json

@@ -0,0 +1,19 @@
+{
+    "title": "API文档",
+    "name": "API - Bot",
+    "description": "Bot",
+    "version": "1.0.0",
+    "url": "",
+
+    "sampleUrl": "http://bot.cn:2316",
+    "sampleUrl": "https://bot.testx2.cc",
+
+
+
+    "template": {
+        "forceLanguage": "zh_cn",
+        "withGenerator": false,
+        "aloneDisplay":false,
+        "showRequiredLabels": false
+    }
+}

+ 32 - 0
app/Console/Kernel.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Console;
+
+use Illuminate\Console\Scheduling\Schedule;
+use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
+
+class Kernel extends ConsoleKernel
+{
+    /**
+     * Define the application's command schedule.
+     *
+     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
+     * @return void
+     */
+    protected function schedule(Schedule $schedule)
+    {
+        // $schedule->command('inspire')->hourly();
+    }
+
+    /**
+     * Register the commands for the application.
+     *
+     * @return void
+     */
+    protected function commands()
+    {
+        $this->load(__DIR__.'/Commands');
+
+        require base_path('routes/console.php');
+    }
+}

+ 123 - 0
app/Constants/Captcha.php

@@ -0,0 +1,123 @@
+<?php
+
+
+namespace App\Constants;
+
+
+class Captcha
+{
+    private $width;
+    private $height;
+    private $codeNum;
+    private $code;
+    private $im;
+
+    //初始化
+    function __construct($width = 80, $height = 20, $codeNum = 4)
+    {
+        $this->width = $width;
+        $this->height = $height;
+        $this->codeNum = $codeNum;
+    }
+
+    //显示验证码
+    function showImg()
+    {
+        //创建图片
+        $this->createImg();
+        //设置干扰元素
+        $this->setDisturb();
+        //设置验证码
+        $this->setCaptcha();
+        //输出图片
+//        $this->outputImg();
+    }
+
+    //获取显示的验证码,用来验证验证码是否数据正确
+    function getCaptcha()
+    {
+        return $this->code;
+    }
+
+    //创建图片
+    private function createImg()
+    {
+        $this->im = imagecreatetruecolor($this->width, $this->height);
+        $bgColor = imagecolorallocate($this->im, 255, 255, 255);//创建的前景为白色
+        imagefill($this->im, 0, 0, $bgColor);
+    }
+
+    //设置干扰元素
+    private function setDisturb()
+    {
+        $area = ($this->width * $this->height) / 20;
+        $disturbNum = ($area > 250) ? 250 : $area;
+        //加入点干扰
+        for ($i = 0; $i < $disturbNum; $i++) {
+            $color = imagecolorallocate($this->im, rand(0, 255), rand(0, 255), rand(0, 255));
+            imagesetpixel($this->im, rand(1, $this->width - 2), rand(1, $this->height - 2), $color);
+        }
+        //加入弧线
+        for ($i = 0; $i <= 1; $i++) {//最多两条线
+            $color = imagecolorallocate($this->im, rand(128, 255), rand(125, 255), rand(100, 255));
+            imagearc($this->im, rand(0, $this->width), rand(0, $this->height), rand(30, 300), rand(20, 200), 50, 30, $color);
+        }
+    }
+
+    //设置验证码随机数
+    private function createCode()
+    {
+        $str = "23456789abcdefghijkmnpqrstuvwxyzABCDEFGHIJKMNPQRSTUVWXYZ";
+        $str = str_split($str);
+        for ($i = 0; $i < $this->codeNum; $i++) {
+            $this->code .= $str[rand(0, sizeof($str) - 1)];
+//            $this->code .= $str{rand(0, strlen($str) - 1)};
+        }
+    }
+
+    //设置验证码
+    private function setCaptcha()
+    {
+        //设置验证码随机数
+        $this->createCode();
+        //文字颜色
+        $color = imagecolorallocate($this->im, rand(50, 250), rand(100, 250), rand(128, 250));
+        //字体文件
+        $fontPath = base_path() . "/public/layuiadmin/layui/font/Monaco.ttf";
+        //图象资源,尺寸,角度,x轴,y轴,颜色,字体路径,文本插入图像
+        imagefttext($this->im, 30, 0, 10, 35, $color, $fontPath, $this->code);
+    }
+
+    //输出图片
+    private function outputImg()
+    {
+        if (imagetypes() & IMG_JPG) {
+//            直接向浏览器输出图片
+            header('Content-type:image/jpeg');
+            imagejpeg($this->im);
+
+        } elseif (imagetypes() & IMG_GIF) {
+            header('Content-type: image/gif');
+            imagegif($this->im);
+
+        } elseif (imagetypes() & IMG_PNG) {
+            header('Content-type: image/png');
+            imagepng($this->im);
+        } else {
+            die("Don't support image type!");
+        }
+    }
+
+    public function getBase64Image()
+    {
+        ob_start();
+        imagepng($this->im);
+        $imageData = ob_get_contents();
+        ob_end_clean();
+        $base64Image = base64_encode($imageData);
+        $base64Image = "data:image/png;base64," . $base64Image;
+        imagedestroy($this->im);
+        return $base64Image;
+    }
+
+}

+ 50 - 0
app/Constants/HttpStatus.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Constants;
+
+class HttpStatus
+{
+    const UNKNOWN_ERROR = -1;
+
+
+    const OK = 0;
+
+    const USER_DOES_NOT_EXIST = 101001;//用户不存在
+    const PASSWORDS_ERROR = 101002;//密码错误
+    const VERIFICATION_CODE_ERROR = 101003;//验证码错误
+    const VERIFICATION_CODE_EXPIRED = 101004;//验证码过期
+    const PASSWORD_INCONSISTENCY = 101005;//密码不一致
+    const USERNAME_ALREADY_EXISTS = 101006;//用户名已存在
+    const EMAIL_ALREADY_EXISTS = 101007;//邮箱已存在
+    const USERNAME_ERROR = 101008;//用户名错误
+    const VALIDATION_FAILED = 101009;//参数验证失败
+    const SYSTEM_ERROR = 101010;//系统错误
+    const AUTHORIZATION_HEADER_NOT_FOUND = 101011;//请登录
+    const NO_COLLECT_YOURSELF = 101012;//禁止收藏自己
+    const NO_BASIC_INFO = 101013;//没有基本信息
+    const NOT_FOUND = 101014;//路由不存在
+    const AVATAR_MUST_SQUARE = 101015;//头像必须是正方形的
+    const PAIRING_FAILURE = 101016;//配对失败
+    const COLLECT_COUNT_REACH_MAX = 101017;
+    const SEND_CODE_ERROR = 101018;
+    const PHONE_ERROR = 101019;
+    const POST_DOES_NOT_EXIST = 101020;
+    const FILE_UPLOAD_ERROR = 101021;
+    const INVITATION_CODE_ERROR = 101022;//邀请码错误
+    const USER_ANOTHER_DEVICE = 101023;//用户已在其他设备登录
+    const INSUFFICIENT = 101024;//剩余抽奖次数不足
+    const MAXIMUM_NUMBER_OF_ADDRESSES = 101025;//地址数量达到上限
+    const HTTP_POST_ERROR = 101026;//post请求错误
+    const IM_ERROR = 101027;//IM 错误
+    const PHONE_ALREADY_EXISTS = 101028;//手机号已存在
+    const GOOGLE_ERROR = 101029;//谷歌登录错误
+    const INSUFFICIENT_CHAT_BALANCE = 101030;//聊天余额不足
+    const INSUFFICIENT_WALLET = 101031;//钱包余额不足
+    const FACEBOOK_ERROR = 101032;//Facebook 错误
+    const VERIFY_ERROR = 101033;//验证资料错误,请检查验证状态是否为待验证
+    const PAY_VERIFY_ERROR = 101034;//支付验证错误
+    const IM_SYSTEM_ERROR =101035;//im系统错误
+    const  CUSTOM_ERROR = -3;
+
+
+}

+ 29 - 0
app/Constants/StepStatus.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Constants;
+
+class StepStatus
+{
+    const INPUT_ROOM_ID = 1;//输入 房间号
+    const INPUT_ROOM_NUM = 2;//输入 人数/局数
+    const INPUT_GAME_ID = 3;//输入 游戏ID
+    const INPUT_GAME_NAME = 4;//输入  游戏名称
+    const INPUT_GAME_INTRODUCTION = 14;//输入  游戏介绍
+    const INPUT_MIDWAY = 15;//选择是否允许中途加入
+
+    const INPUT_SCORE = 5;//输入 结算分数
+    const INPUT_IMAGE = 6;//上传 战绩截图
+
+    const INPUT_WITHDRAW_MONEY = 7;//输入 提现金额
+    const CHOOSE_WITHDRAW_ADDRESS = 8;//选择 提现地址
+
+    const INPUT_ADDRESS_TRC20 = 9;//输入 TRC20地址
+    const INPUT_ADDRESS_ALIAS = 10;//输入 地址别名
+
+    const INPUT_JOIN_ROOM_ID = 11;//输入房间号加入房间
+
+    const INPUT_TOP_UP_MONEY = 12;//输入 充值金额
+    const INPUT_TOP_UP_IMAGE = 13;//上传 充值截图
+
+
+}

+ 65 - 0
app/Constants/Util.php

@@ -0,0 +1,65 @@
+<?php
+
+
+namespace App\Constants;
+
+
+use Illuminate\Support\Facades\Cache;
+
+class Util
+{
+
+//    public static function defaultBackgroundImage()
+////    {
+////        $array = ['/static/img/background_image.png', '/static/img/background_image1.png', '/static/img/background_image2.png'];
+////        $key = array_rand($array);
+////        return $array[$key];
+////    }
+
+    static function delCache($chatId)
+    {
+        Cache::delete(get_step_key($chatId));
+    }
+
+    static function iso3166Alpha2Codes()
+    {
+        return [
+            'AF', 'AX', 'AL', 'DZ', 'AS', 'AD', 'AO', 'AI', 'AQ', 'AG', 'AR', 'AM', 'AW', 'AU', 'AT', 'AZ',
+            'BS', 'BH', 'BD', 'BB', 'BY', 'BE', 'BZ', 'BJ', 'BM', 'BT', 'BO', 'BQ', 'BA', 'BW', 'BR', 'IO',
+            'BN', 'BG', 'BF', 'BI', 'CV', 'KH', 'CM', 'CA', 'KY', 'CF', 'TD', 'CL', 'CN', 'CX', 'CC', 'CO',
+            'KM', 'CG', 'CD', 'CK', 'CR', 'CI', 'HR', 'CU', 'CW', 'CY', 'CZ', 'DK', 'DJ', 'DM', 'DO', 'EC',
+            'EG', 'SV', 'GQ', 'ER', 'EE', 'SZ', 'ET', 'FK', 'FO', 'FJ', 'FI', 'FR', 'GF', 'PF', 'TF', 'GA',
+            'GM', 'GE', 'DE', 'GH', 'GI', 'GR', 'GL', 'GD', 'GP', 'GU', 'GT', 'GG', 'GN', 'GW', 'GY', 'HT',
+            'HM', 'VA', 'HN', 'HK', 'HU', 'IS', 'IN', 'ID', 'IR', 'IQ', 'IE', 'IM', 'IL', 'IT', 'JM', 'JP',
+            'JE', 'JO', 'KZ', 'KE', 'KI', 'KP', 'KR', 'KW', 'KG', 'LA', 'LV', 'LB', 'LS', 'LR', 'LY', 'LI',
+            'LT', 'LU', 'MO', 'MG', 'MW', 'MY', 'MV', 'ML', 'MT', 'MH', 'MQ', 'MR', 'MU', 'YT', 'MX', 'FM',
+            'MD', 'MC', 'MN', 'ME', 'MS', 'MA', 'MZ', 'MM', 'NA', 'NR', 'NP', 'NL', 'NC', 'NZ', 'NI', 'NE',
+            'NG', 'NU', 'NF', 'MK', 'MP', 'NO', 'OM', 'PK', 'PW', 'PS', 'PA', 'PG', 'PY', 'PE', 'PH', 'PN',
+            'PL', 'PT', 'PR', 'QA', 'RE', 'RO', 'RU', 'RW', 'BL', 'SH', 'KN', 'LC', 'MF', 'PM', 'VC', 'WS',
+            'SM', 'ST', 'SA', 'SN', 'RS', 'SC', 'SL', 'SG', 'SX', 'SK', 'SI', 'SB', 'SO', 'ZA', 'GS', 'SS',
+            'ES', 'LK', 'SD', 'SR', 'SJ', 'SE', 'CH', 'SY', 'TW', 'TJ', 'TZ', 'TH', 'TL', 'TG', 'TK', 'TO',
+            'TT', 'TN', 'TR', 'TM', 'TC', 'TV', 'UG', 'UA', 'AE', 'GB', 'US', 'UM', 'UY', 'UZ', 'VU', 'VE',
+            'VN', 'VG', 'VI', 'WF', 'EH', 'YE', 'ZM', 'ZW'
+        ];
+    }
+
+
+    public static function ensureUrl($url)
+    {
+        if (!filter_var($url, FILTER_VALIDATE_URL)) {
+            if (empty($url)) return $url;
+            $newUrl = config('app.url') . $url;
+            return filter_var($newUrl, FILTER_VALIDATE_URL) ? $newUrl : $url;
+        }
+        return $url;
+    }
+
+    public static function replacePartInUrl($url)
+    {
+        if (filter_var($url, FILTER_VALIDATE_URL)) {
+            $target = config('app.url');
+            return str_replace($target, '', $url);
+        }
+        return $url;
+    }
+}

+ 50 - 0
app/Exceptions/Handler.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Exceptions;
+
+use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
+use Throwable;
+
+class Handler extends ExceptionHandler
+{
+    /**
+     * A list of exception types with their corresponding custom log levels.
+     *
+     * @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
+     */
+    protected $levels = [
+        //
+    ];
+
+    /**
+     * A list of the exception types that are not reported.
+     *
+     * @var array<int, class-string<\Throwable>>
+     */
+    protected $dontReport = [
+        //
+    ];
+
+    /**
+     * A list of the inputs that are never flashed to the session on validation exceptions.
+     *
+     * @var array<int, string>
+     */
+    protected $dontFlash = [
+        'current_password',
+        'password',
+        'password_confirmation',
+    ];
+
+    /**
+     * Register the exception handling callbacks for the application.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        $this->reportable(function (Throwable $e) {
+            //
+        });
+    }
+}

+ 10 - 0
app/Exceptions/MessageException.php

@@ -0,0 +1,10 @@
+<?php
+
+
+namespace App\Exceptions;
+
+
+class MessageException extends \Exception
+{
+
+}

+ 8 - 0
app/Exceptions/ValidationFailedException.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace App\Exceptions;
+
+class ValidationFailedException extends \Exception
+{
+
+}

+ 46 - 0
app/Helpers/Sign.php

@@ -0,0 +1,46 @@
+<?php
+use kornrunner\Secp256k1;
+use kornrunner\Serializer\HexSignatureSerializer;
+use kornrunner\Signature\Signature;
+use kornrunner\Util\Buffer;
+
+
+
+
+function signTransaction(array $transaction, string $privateKey): array {
+    $rawData = $transaction['raw_data'];
+
+    // 1. 将 raw_data 转为 JSON 字符串
+    $rawDataJson = json_encode($rawData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+    if ($rawDataJson === false) {
+        throw new \Exception("Invalid raw_data: " . json_last_error_msg());
+    }
+
+    // 2. 计算 SHA256 哈希(二进制形式)
+    $msgHash = hash('sha256', $rawDataJson, true); // raw binary
+
+    // 3. 构造私钥 buffer(二进制)
+    $privateKeyBin = hex2bin($privateKey);
+    if ($privateKeyBin === false || strlen($privateKeyBin) !== 32) {
+        throw new \Exception("Invalid private key format");
+    }
+
+    // 4. 签名
+    $secp256k1 = new Secp256k1();
+    $signature = $secp256k1->sign($msgHash, $privateKeyBin);
+
+    // 5. 生成 HEX 签名(包含 recovery ID)
+    $serializer = new HexSignatureSerializer();
+    $fullHex = $serializer->serialize($signature); // 130 hex chars: r + s + v (65 bytes)
+
+    // ⚠️ Tron 要求 r + s(不带 v) => 64 字节,即 128 字符的 hex 字符串
+    $r = substr($fullHex, 0, 64);
+    $s = substr($fullHex, 64, 64);
+    $sigHex = $r . $s;
+
+    return [
+        'txID' => $transaction['txID'],
+        'raw_data' => $transaction['raw_data'],
+        'signature' => [$sigHex],
+    ];
+}

+ 1127 - 0
app/Helpers/TronHelper.php

@@ -0,0 +1,1127 @@
+<?php
+
+namespace App\Helpers;
+
+use Mdanter\Ecc\EccFactory;
+use Mdanter\Ecc\Random\RandomGeneratorFactory;
+use Illuminate\Support\Facades\Crypt;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
+use Illuminate\Support\Facades\Log;
+use TronTool\Credential;
+use TronTool\TronApi;
+use TronTool\TronKit;
+use kornrunner\Secp256k1;
+use kornrunner\Keccak;
+use App\Helpers\TronRawSerializer;
+use App\Http\Controllers\admin\Balance;
+use kornrunner\Signature\Signature;
+use Elliptic\EC;
+use kornrunner\Serializer\HexSignatureSerializer;
+
+
+class TronHelper
+{
+    public static $isTest = true;
+    public static $tronUrl = 'https://api.trongrid.io';
+    public static $nileUrl = 'https://nile.trongrid.io'; // 测试网
+
+    const CONFIRMED_NUMBER = 3; // 确认数
+
+    protected static $client;
+
+    public static function init()
+    {
+
+       
+        if(self::getTronNetwork()){
+            $url = self::$nileUrl;
+        }else{
+            $url = self::$tronUrl;
+        }
+        if (!self::$client) {
+            self::$client = new Client([
+                'base_uri' => $url,
+                'timeout'  => 10.0,
+                'headers'  => [
+                    'Accept' => 'application/json',
+                    'Content-Type' => 'application/json'
+                ],
+                'verify' => false
+            ]);
+        }
+    }
+
+    /**
+     * @description: 获取网络
+     * @return {*}
+     */    
+    public static function getTronNetwork()
+    {
+        $netWork = config('app.tron_network'); // 默认是主网
+        if($netWork == 'main'){
+            return false;
+        }else{
+            return true;
+        }
+    }
+
+    /**
+     * @description: 创建钱包地址和私钥
+     * @return {private_key,address} 
+     */    
+    public static function createAddress($memberId = ''): array
+    {
+         // 生成 TRC20 私钥
+        $privateKey = self::makePrivateKey($memberId); // 默认不带 memberId
+        
+        // 从私钥生成地址
+        $address = self::getAddressByPrivateKey($privateKey);
+        
+         // 记录日志
+        Log::channel('wallet')->info('创建钱包地址', [
+            'member_id' => $memberId,
+            'address' => $address,
+            'private_key' => $privateKey,
+        ]);
+        return [
+            'address' => $address,
+            'private_key' => $privateKey,
+        ];
+
+    }
+
+
+    /**
+     * @description: 转账USDT
+     * @param {*} $privateKey   转账秘钥
+     * @param {*} $toAddress    接收地址
+     * @param {*} $amount       转账金额
+     * @return {*}
+     */    
+    public static function transferUSDT($privateKey, $toAddress, $amount)
+    {
+        $nodeScript = base_path('node/index.js'); // Laravel根目录下的路径
+        $nodeBin = 'node'; // 用 which node 查看,或自定义
+        if(self::getTronNetwork()){
+            $fullNodeUrl = self::$nileUrl;
+        }else{
+            $fullNodeUrl = self::$tronUrl;
+        }
+        $contractAddress = self::getContractAddress('USDT');
+        $cmd = sprintf(
+            '%s %s %s %s %s %s %s 2>&1',
+            escapeshellcmd($nodeBin),
+            escapeshellarg($nodeScript),
+            escapeshellarg($privateKey),
+            escapeshellarg($toAddress),
+            escapeshellarg($contractAddress),
+            escapeshellarg($fullNodeUrl),
+            escapeshellarg($amount)
+        );
+
+        exec($cmd, $outputLines, $status);
+        $output = implode("\n", $outputLines);
+        $result = json_decode($output, true);
+        return $result;
+        // self::init();
+
+        // $contractAddress = self::getContractAddress('USDT');
+
+        // $fromAddress = self::getAddressByPrivateKey($privateKey);
+        
+
+        // // 1. 构建转账交易
+        // $transferData = [
+        //     'owner_address'     => self::base58check2HexString($fromAddress),
+        //     'contract_address'  => self::base58check2HexString($contractAddress),
+        //     'function_selector' => 'transfer(address,uint256)',
+        //     'parameter'         => self::buildParameter($toAddress, $amount),
+        //     'visible'           => false
+        // ];
+   
+        
+       
+        // $response = self::$client->post('/wallet/triggersmartcontract', [
+        //     'json' => $transferData
+        // ]);
+        // $data = json_decode($response->getBody(), true);
+        // var_dump($data);
+
+ 
+        // if (empty($data['transaction'])) {
+        //     throw new \Exception('构建转账交易失败: ' . json_encode($data));
+        // }
+
+        // $txid = $data['transaction']['txID'];
+        
+        // // 2. 签名交易
+        // // $rawData = $data['transaction']['raw_data'];
+        // // $rawDataBytes = TronRawSerializer::serializeRawData($rawData); // 自定义函数,返回 Protobuf 序列化后的二进制
+        // // $txHash = hash('sha256', $rawDataBytes); // Tron使用 SHA256,不是 Keccak256
+        // // // 使用私钥签名
+        // // $secp256k1 = new Secp256k1();
+        // // $signature = $secp256k1->sign($txHash, $privateKey);
+        // // $signatureHex = self::formatTronSignature($signature);
+
+        // $signedTx = self::signTronTransaction($data['transaction'],$privateKey);
+        // // var_dump($signatureHex);
+        
+        // // $signedTx['visible'] = true;
+        // // 3. 广播交易
+        // // 将 signature 转成 hex 字符串
+        // // $signatureHex = bin2hex($signature);
+        // // $signatureHex = $signature;
+        // // var_dump($signatureHex);
+        // // 构建签名交易
+        // // $signedTx = [
+        // //     'txID'     => $data['transaction']['txID'],
+        // //     'raw_data' => $data['transaction']['raw_data'],
+        // //     'signature' => $signatureHex,
+        // // ];
+        // var_dump($signedTx);
+        // $broadcast = self::$client->post('/wallet/broadcasttransaction', [
+        //     'json' => $signedTx
+        // ]);
+        // $broadcastData = json_decode($broadcast->getBody(), true);
+
+        // return $broadcastData;
+    }
+
+    public static function signTronTransaction(array $transaction, string $privateKey): array
+    {
+        $rawData = $transaction['raw_data'] ?? null;
+        if (!$rawData) {
+            throw new \Exception('交易中没有 raw_data');
+        }
+
+        $rawDataBytes = TronRawSerializer::serializeRawData($rawData); 
+
+        $txHash = hash('sha256', $rawDataBytes, true); // 二进制哈希
+
+        $privateKeyHex = ltrim($privateKey, '0x');
+        if (strlen($privateKeyHex) !== 64) {
+            throw new \Exception('私钥格式不正确');
+        }
+        $privateKeyBin = hex2bin($privateKeyHex);
+
+        $secp256k1 = new Secp256k1();
+        $signatureObj = $secp256k1->sign($txHash, $privateKeyBin);
+
+        // r, s 都是 GMP 对象,转成32字节二进制字符串
+        $rBin = self::gmpToBin32($signatureObj->getR());
+        $sBin = self::gmpToBin32($signatureObj->getS());
+
+        $signatureBin = $rBin . $sBin; // 64字节二进制签名
+
+        $transaction['signature'] = [$signatureBin];
+
+        return $transaction;
+    }
+
+    private static function gmpToBin32($gmpNum): string
+    {
+        $hex = gmp_strval($gmpNum, 16);
+        if (strlen($hex) % 2 !== 0) {
+            $hex = '0' . $hex;
+        }
+        $bin = hex2bin($hex);
+        return str_pad($bin, 32, "\0", STR_PAD_LEFT);
+    }
+
+    /**
+     * 构建 parameter 字符串(目标地址 + 金额)
+     */
+    protected static function buildParameter($toAddress, $amount)
+    {
+        $hexAddress = self::base58check2HexString($toAddress);
+        $addr = str_pad(substr($hexAddress, 2), 64, '0', STR_PAD_LEFT); // 去掉 '41' 开头的 Tron 前缀
+        $amt  = str_pad(bcdechex(bcmul($amount, '1000000')), 64, '0', STR_PAD_LEFT); // USDT 精度 6
+
+        return $addr . $amt;
+    }
+
+
+    // /**
+    //  * @param Signature $signature
+    //  * @return string 65字节签名的十六进制字符串
+    //  */
+    // public static function formatTronSignature(Signature $signature): string
+    // {
+    //     $r = gmp_strval($signature->getR(), 16);
+    //     $s = gmp_strval($signature->getS(), 16);
+    //     $v = dechex($signature->getRecoveryParam()); // v 即 recoveryParam
+
+    //     // 补齐 r 和 s 为 64位长度(即32字节)
+    //     $r = str_pad($r, 64, '0', STR_PAD_LEFT);
+    //     $s = str_pad($s, 64, '0', STR_PAD_LEFT);
+    //     $v = str_pad($v, 2, '0', STR_PAD_LEFT); // 1字节 = 2个hex字符
+      
+    //     $v_decimal = hexdec($v); // 0
+    //     $v_decimal += 27;        // 27
+    //     $v_hex = dechex($v_decimal); // "1b"
+    //     return $r . $s . $v_hex; // 返回最终的签名字符串
+    // }
+
+    /**
+     * @description: 获取地址的YRX余额
+     * @param {*} $addressBase58
+     * @return {*}
+     */    
+    public static function getTrxBalance($addressBase58)
+    {
+        $hexAddress = TronHelper::base58check2HexString($addressBase58);
+        
+        self::init();
+
+        $response = self::$client->post('/wallet/getaccount', [
+            'json' => ['address' => $hexAddress]
+        ]);
+
+        $data = json_decode($response->getBody(), true);
+        
+        if (!empty($data['balance'])) {
+            // TRX 的单位是 sun,1 TRX = 1_000_000 sun
+            return $data['balance'] / 1_000_000;
+        }
+
+        return 0;
+    }
+
+    // /**
+    //  * @description: 向账户转TRX能量
+    //  * @param {*} $fromAddress
+    //  * @param {*} $privateKey
+    //  * @param {*} $toAddress
+    //  * @param {*} $amount
+    //  * @return {*}
+    //  */    
+    // public static function transferTRX($fromAddress, $privateKey, $toAddress, $amount)
+    // {
+    //     self::init();
+
+    //     $ownerAddressHex = self::base58check2HexString($fromAddress);
+    //     $toAddressHex = self::base58check2HexString($toAddress);
+
+    //     $createData = [
+    //         'owner_address' => $ownerAddressHex,
+    //         'to_address' => $toAddressHex,
+    //         'amount' => (int)($amount * 1_000_000), // TRX 精度为 6
+    //     ];
+
+        
+    //     // 构建转账交易
+    //     $response = self::$client->post('/wallet/createtransaction', [
+    //         'json' => $createData
+    //     ]);
+
+    //     $tx = json_decode($response->getBody(), true);
+       
+    //     if (!isset($tx['txID'])) {
+    //         throw new \Exception('创建转账交易失败: ' . json_encode($tx));
+    //     }
+   
+
+    //     // 签名交易
+    //     $signature = self::signTransaction($tx,$privateKey);
+
+    //     // 广播交易
+    //     $signedTx = [
+    //         'txID'     => $tx['txID'],
+    //         'raw_data' => $tx['raw_data'],
+    //         'raw_data_hex' => $tx['raw_data_hex'],
+    //         'signature' => $signature,
+    //     ];
+
+
+    //     $response = self::$client->post('/wallet/broadcasttransaction', [
+    //         'json' => $signedTx
+    //     ]);
+
+    //     var_dump($signedTx);
+
+
+    //     $result = json_decode($response->getBody(), true);
+    //     var_dump($result);
+    //     die();
+    //     // if (!isset($result['result']) || !$result['result']) {
+    //     //     throw new \Exception('广播失败: ' . json_encode($result));
+    //     // }
+
+    //     return [
+    //         'txID' => $tx['txID'],
+    //         'result' => true
+    //     ];
+    // }
+
+    // /**
+    //  * @description: TRX签名
+    //  * @param {array} $transaction
+    //  * @param {string} $privateKey
+    //  * @return {*}
+    //  */    
+    // public static function signTransaction($tx, $privateKey)
+    // {
+    //     if (empty($tx['raw_data_hex'])) {
+    //         throw new \Exception('原始交易数据为空,无法签名');
+    //     }
+
+    //     $rawDataHex = $tx['raw_data_hex'];
+    //     $rawDataBin = hex2bin($rawDataHex);
+    //     $hash = hash('sha256', $rawDataBin, true); // 原始数据做 SHA256
+
+    //     $signatureDer = self::ecdsaSignRaw($hash, $privateKey); // 返回 DER 格式签名
+    //     return [$signatureDer]; // Tron 要求是签名数组
+    // }
+
+    // public static function ecdsaSignRaw($hash, $privateKeyHex)
+    // {
+    //     $ec = new EC('secp256k1');
+    //     $key = $ec->keyFromPrivate($privateKeyHex);
+
+    //     $signature = $key->sign(bin2hex($hash), ['canonical' => true]);
+
+    //     $r = str_pad($signature->r->toString('hex'), 64, '0', STR_PAD_LEFT);
+    //     $s = str_pad($signature->s->toString('hex'), 64, '0', STR_PAD_LEFT);
+
+    //     return hex2bin($r . $s); // 返回64字节的二进制字符串
+    // }
+
+    /**
+     * 查询某地址的 TRX 充值记录
+     * @param string $address 充值地址
+     * @param int $limit 返回记录数
+     * @return array
+     */
+    public static function getTrxTransactions(string $address, int $limit = 30): array
+    {
+
+        self::init();
+
+        $response = self::$client->get("https://nile.trongrid.io/v1/accounts/{$address}/transactions", [
+            'query' => [
+                'limit' => $limit,
+                'only_confirmed' => 'true',
+                'order' => 'desc',
+            ]
+        ]);
+
+        $data = json_decode($response->getBody()->getContents(), true);
+        
+        return $data;
+    }
+
+    /**
+     * 查询某地址的 TRC20 USDT 充值记录
+     * @param string $address 充值地址
+     * @param int $limit 返回记录数
+     * @return array
+     */
+    public static function getTrc20UsdtRecharges(string $address, int $limit = 30): array
+    {
+
+        self::init();
+
+        $usdtContract = self::getContractAddress('USDT');
+        
+        $url = "/v1/accounts/{$address}/transactions/trc20?limit={$limit}";
+
+        
+        // try {
+            $response = self::$client->get($url, [
+                'headers' => [
+                    'Accept' => 'application/json',
+                ],
+                'timeout' => 10,
+            ]);
+            $data = json_decode($response->getBody()->getContents(), true);
+            
+
+            $recharges = [];
+            foreach ($data['data'] ?? [] as $item) {
+                $tokenAddress = $item['token_info']['address'] ?? '';
+                if (strtolower($tokenAddress) !== strtolower($usdtContract)) {
+                    continue; // 不是USDT
+                }
+
+                if($item['to'] == $address){
+                    $recharges[] = [
+                        'txid'         => $item['transaction_id'],
+                        'from_address'         => $item['from'],
+                        'to_address'           => $item['to'],
+                        'amount'       => bcdiv($item['value'], bcpow('10', $item['token_info']['decimals']), 6),
+                        'coin'       => $item['token_info']['symbol'],
+                        'block_time'    => intval($item['block_timestamp'] / 1000),
+                    ];
+                }
+
+            }
+            return $recharges;
+        // } catch (\Exception $e) {
+        //     return ['error' => $e->getMessage()];
+        // }
+    }
+
+
+    /**
+     * @description: 获取TRC20账户余额
+     * @param {*} $address
+     * @return {*}
+     */    
+    public static function getTrc20Balance($address)
+    {
+        self::init();
+
+        $contractAddress = self::getContractAddress('USDT');
+
+        $hexAddress = self::base58check2HexString($address);
+
+        $postData = [
+            'owner_address'     => $hexAddress,
+            'contract_address'  => self::base58check2HexString($contractAddress),
+            'function_selector' => 'balanceOf(address)',
+            'parameter'         => str_pad(substr($hexAddress, 2), 64, '0', STR_PAD_LEFT),
+        ];
+
+        // try {
+            $response = self::$client->post('/wallet/triggerconstantcontract', [
+                'headers' => [
+                    'Content-Type' => 'application/json',
+                    'TRON-PRO-API-KEY' => '3b241a9c-0076-49bf-b883-a003c97c19f6', // 添加这一行
+                ],
+                'json' => $postData,
+            ]);
+
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body, true);
+            
+            $hexBalance = $data['constant_result'][0] ?? '0x0';
+
+            return hexdec($hexBalance) / 1e6; // USDT 精度是 6 位
+        // } catch (\Exception $e) {
+        //     // 可以记录日志或抛出异常
+        //     return 0;
+        // }
+    }
+
+
+    /**
+     * 查询交易确认数
+     * @param string $txid 交易哈希
+     * @return array
+     */
+    public static function getTransactionConfirmations(string $txid): array
+    {
+        // $client = new Client(['timeout' => 10]);
+
+        self::init();
+
+        try {
+            // 1. 获取交易信息
+            $txResp = self::$client->post('/wallet/gettransactioninfobyid', [
+                'json' => ['value' => $txid],
+                'headers' => ['Accept' => 'application/json'],
+            ]);
+            $txData = json_decode($txResp->getBody()->getContents(), true);
+
+            if (!isset($txData['blockNumber'])) {
+                return ['success' => false, 'message' => '交易尚未被打包或无效'];
+            }
+
+            $txBlock = $txData['blockNumber'];
+
+            // 2. 获取最新区块高度
+            $blockResp = self::$client->get('/wallet/getnowblock', [
+                'headers' => ['Accept' => 'application/json'],
+            ]);
+            $blockData = json_decode($blockResp->getBody()->getContents(), true);
+            $latestBlock = $blockData['block_header']['raw_data']['number'] ?? 0;
+
+            if ($latestBlock == 0) {
+                return ['success' => false, 'message' => '无法获取最新区块'];
+            }
+
+            // 3. 计算确认数
+            $confirmations = $latestBlock - $txBlock + 1;
+
+            return [
+                'success' => true,
+                'txid' => $txid,
+                'block_number' => $txBlock,
+                'latest_block' => $latestBlock,
+                'confirmations' => $confirmations,
+            ];
+        } catch (\Exception $e) {
+            return ['success' => false, 'message' => $e->getMessage()];
+        }
+    }
+
+
+    /**
+     * 加密私钥
+     */
+    public static function encryptPrivateKey(string $privateKeyHex): string
+    {
+        return Crypt::encryptString($privateKeyHex);
+    }
+
+    /**
+     * 解密私钥
+     */
+    public static function decryptPrivateKey(string $encrypted): string
+    {
+        return Crypt::decryptString($encrypted);
+    }
+
+    /**
+     * 获取合约地址
+     * @param $currency
+     * @return string
+     */
+    public static function getContractAddress($currency)
+    {
+        $currency = strtoupper($currency);
+        switch ($currency) {
+            case 'USDT'://usdt
+                if(self::getTronNetwork()){
+                    $contractAddress = 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf'; // nile测试地址
+                }else{
+                    $contractAddress = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
+                }
+                break;
+            case 'USDC'://usdc
+                $contractAddress = 'TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8';
+                break;
+            case 'USDJ'://USDJ
+                $contractAddress = 'TMwFHYXLJaRUPeW6421aqXL4ZEzPRFGkGT';
+                break;
+            case 'TUSD'://TUSD
+                $contractAddress = 'TUpMhErZL2fhh4sVNULAbNKLokS4GjC1F4';
+                break;
+            case 'BTC'://BTC
+                $contractAddress = 'TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9';
+                break;
+            case 'ETH'://ETH
+                $contractAddress = 'THb4CqiFdwNHsWsQCs4JhzwjMWys4aqCbF';
+                break;
+            case 'USDD'://USDD
+                $contractAddress = 'TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn';
+                break;
+            default:
+                $contractAddress = '';
+        }
+
+        return $contractAddress;
+    }
+
+
+    public static function base58_encode($string)
+    {
+        if (is_string($string) === false) {
+            return false;
+        }
+        if ($string === '') {
+            return '';
+        }
+
+        $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
+        $base = strlen($alphabet);
+
+        $bytes = array_values(unpack('C*', $string));
+        $decimal = $bytes[0];
+        for ($i = 1, $l = count($bytes); $i < $l; $i++) {
+            $decimal = bcmul($decimal, 256);
+            $decimal = bcadd($decimal, $bytes[$i]);
+        }
+        $output = '';
+        while ($decimal >= $base) {
+            $div = bcdiv($decimal, $base, 0);
+            $mod = bcmod($decimal, $base);
+            $output .= $alphabet[$mod];
+            $decimal = $div;
+        }
+        if ($decimal > 0) {
+            $output .= $alphabet[$decimal];
+        }
+        $output = strrev($output);
+        foreach ($bytes as $byte) {
+            if ($byte === 0) {
+                $output = $alphabet[0].$output;
+                continue;
+            }
+            break;
+        }
+
+        return $output;
+    }
+
+    public static function base58_decode($base58)
+    {
+        if (is_string($base58) === false) {
+            return false;
+        }
+        if ($base58 === '') {
+            return '';
+        }
+
+        $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
+        $base = strlen($alphabet);
+
+        $indexes = array_flip(str_split($alphabet));
+        $chars = str_split($base58);
+        foreach ($chars as $char) {
+            if (isset($indexes[$char]) === false) {
+                return false;
+            }
+        }
+        $decimal = $indexes[$chars[0]];
+        for ($i = 1, $l = count($chars); $i < $l; $i++) {
+            $decimal = bcmul($decimal, $base);
+            $decimal = bcadd($decimal, $indexes[$chars[$i]]);
+        }
+        $output = '';
+        while ($decimal > 0) {
+            $byte = bcmod($decimal, 256);
+            $output = pack('C', $byte).$output;
+            $decimal = bcdiv($decimal, 256, 0);
+        }
+        foreach ($chars as $char) {
+            if ($indexes[$char] === 0) {
+                $output = "\x00".$output;
+                continue;
+            }
+            break;
+        }
+
+        return $output;
+    }
+
+//encode address from byte[] to base58check string
+    public static function base58check_en($address)
+    {
+        $hash0 = hash("sha256", $address);
+        $hash1 = hash("sha256", hex2bin($hash0));
+        $checksum = substr($hash1, 0, 8);
+        $address .= hex2bin($checksum);
+
+        return self::base58_encode($address);
+    }
+
+    public static function base58check_de($base58add)
+    {
+        $address = self::base58_decode($base58add);
+        $size = strlen($address);
+        if ($size !== 25) {
+            return false;
+        }
+        $checksum = substr($address, 21);
+        $address = substr($address, 0, 21);
+        $hash0 = hash("sha256", $address);
+        $hash1 = hash("sha256", hex2bin($hash0));
+        $checksum0 = substr($hash1, 0, 8);
+        $checksum1 = bin2hex($checksum);
+        if (strcmp($checksum0, $checksum1)) {
+            return false;
+        }
+
+        return $address;
+    }
+
+    public static function hexString2Base58check($hexString)
+    {
+        $address = hex2bin($hexString);
+
+        return self::base58check_en($address);
+    }
+
+    public static function base58check2HexString($base58add)
+    {
+        $address = self::base58check_de($base58add);
+        return bin2hex($address);
+    }
+
+    public static function hexString2Base64($hexString)
+    {
+        $address = hex2bin($hexString);
+
+        return base64_encode($address);
+    }
+
+    public static function base642HexString($base64)
+    {
+        $address = base64_decode($base64);
+
+        return bin2hex($address);
+    }
+
+    public static function base58check2Base64($base58add)
+    {
+        $address =self::base58check_de($base58add);
+
+        return base64_encode($address);
+    }
+
+    public static function base642Base58check($base64)
+    {
+        $address = base64_decode($base64);
+
+        return self::base58check_en($address);
+    }
+
+    public static function getrandstr($length)
+    {
+        $str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890';
+        $randStr = str_shuffle($str);//打乱字符串
+
+        return substr($randStr, 0, $length);
+    }
+
+    /**
+     * 检测是否有效钱包地址
+     * @param $address
+     * @return bool
+     */
+    public static function isValidAddress($address)
+    {
+        if (empty($address)) {
+            return false;
+        }
+        if (strlen($address) != 34) {
+            return false;
+        }
+        if (substr($address, 0, 1) != 'T') {
+            return false;
+        }
+        // 验证地址
+        $client = new Client([
+            'verify' => false, // 关闭 SSL 验证
+        ]);
+        $response = $client->request('POST', 'https://api.shasta.trongrid.io/wallet/validateaddress', [
+            'body' => json_encode(["address" => $address]),
+            'headers' => [
+                'Accept' => 'application/json',
+                'Content-Type' => 'application/json',
+            ],
+        ]);
+        $resp = $response->getBody()->getContents();
+        $resp = json_decode($resp);
+        // 失败提示错误
+        if ($resp->result === false) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 获取私钥,通过私钥可以拿到收款地址,各种权限
+     * 注意:必须拿28位字符串来生成地址,否则无法生成正常的地址
+     * @param $base64
+     * @return string
+     */
+    public function getPrivateKey($base64)
+    {
+        return $this->base642HexString($base64);
+    }
+
+    /**
+     * 生成波场key
+     * @param $memberId
+     * @return string
+     */
+    public static function makePrivateKey($memberId)
+    {
+        $fix = 'SSTGsstg';//固定8前缀,避免和其他人同地址
+        $rand = TronHelper::getrandstr(20);//10位数随机,避免同地址
+        $base64Sting = $fix.$rand.str_pad($memberId, 15, 0, STR_PAD_LEFT);
+        $tronHelper = new TronHelper();
+        
+
+        return $tronHelper->getPrivateKey($base64Sting);
+    }
+
+
+    /**
+     * 查询余额
+     * @param $privateKey
+     * @return int
+     */
+    public static function getBalance($address, $currency)
+    {
+        bcscale(6);
+        $rate = 1000000;
+        $currency = strtoupper($currency);
+        if(self::getTronNetwork()){
+            $api = TronApi::testNetNilo();
+        }else{
+            $api = TronApi::mainNet();
+        }
+       
+        $privateKey = self::makePrivateKey(time());
+        $credential = Credential::fromPrivateKey($privateKey);
+       
+        $kit = new TronKit($api, $credential);
+       
+        if ($currency == 'TRX') {
+            try {
+                $balance = $kit->getTrxBalance($address);
+                
+                return bcdiv($balance, $rate);
+            } catch (\Exception $e) {
+                return bcmul(0, 0);
+            }
+        }
+        try {
+            $contractAddress = self::getContractAddress($currency);
+           
+            $usdt = $kit->Trc20($contractAddress);
+
+            $balance = $usdt->balanceOf($address);
+
+            return bcdiv($balance, $rate);
+        } catch (\Exception $e) {
+            return bcmul(0, 0);
+        }
+    }
+
+    /**
+     * 获取转账信息
+     * @param $txid
+     */
+    public static function getSendInfo($txid)
+    {
+        // $api = TronApi::mainNet();
+        if(self::getTronNetwork()){
+            $api = TronApi::testNetNilo();
+        }else{
+            $api = TronApi::mainNet();
+        }
+        $ret = $api->getTransaction($txid);
+        $retArr = $ret->ret;
+        if (isset($retArr[0]->contractRet)) {
+            return $retArr[0]->contractRet;
+        }
+
+        return 'UNKNOWN';
+    }
+
+    /**
+     * 完整交易详情,如果交易未完成或者不成功,返回空
+     * @param $txid
+     * @return string
+     */
+    public static function getTransactionInfoById($txid)
+    {
+        // $api = TronApi::mainNet();
+        if(self::getTronNetwork()){
+            $api = TronApi::testNetNilo();
+        }else{
+            $api = TronApi::mainNet();
+        }
+        $ret = $api->getTransactionInfoById($txid);
+
+        return json_decode(json_encode($ret), true);
+    }
+
+    /**
+     * 获取出款账户地址
+     */
+    public static function getWithdrawAddress()
+    {
+        // $privateKey = Yii::$app->params['withdraw_address_trx_key'];
+        // $credential = Credential::fromPrivateKey($privateKey);
+
+        // return $credential->address()->base58();
+    }
+
+    /**
+     * 获取地址
+     * @param $fromKey
+     */
+    public static function getAddressByPrivateKey($fromKey)
+    {
+       
+        $credential = Credential::fromPrivateKey($fromKey);
+
+        return $credential->address()->base58();
+    }
+
+    /**
+     * 转账给指定地址
+     * @param $fromKey
+     * @param $toAddress
+     * @param $amount
+     * @throws \Exception
+     */
+    public static function sendTrx($fromKey, $toAddress, $amount)
+    {
+        if ($amount <= 0) {
+            throw new \Exception('不能低于 0 trx');
+        }
+        // from 初始化
+        if(self::getTronNetwork()){
+            $api = TronApi::testNetNilo();
+        }else{
+            $api = TronApi::mainNet();
+        }
+        $credential = Credential::fromPrivateKey($fromKey);
+        $kit = new TronKit($api, $credential);
+        // from 余额判断
+        $from = $credential->address()->base58();
+        $balance = self::getBalance($from, 'TRX');
+        $rate = 1000000;
+        if ($balance < $amount) {
+            return '余额不足';
+        }
+        // 发送
+        try {
+            return $kit->sendTrx($toAddress, 1 * bcmul($amount, $rate, 6));
+        } catch (\Exception $e) {
+            Log::error('转账错误,fromKey='.$fromKey.'toAddress='.$toAddress.'amount='.$amount);
+            Log::error('转账错误'.$e->getMessage());
+
+            return false;
+        }
+    }
+
+    /**usdt转账
+     * @param $fromKey
+     * @param $toAddress
+     * @param $amount
+     * @param $currency
+     * @return bool|object
+     * @throws \Exception
+     */
+    public static function sendTrc20($fromKey, $toAddress, $amount, $currency)
+    {
+        if ($amount <= 0) {
+            throw new \Exception('不能低于 0 USDT');
+        }
+        $credential = Credential::fromPrivateKey($fromKey);
+        $credential->address()->hex();
+
+        if(self::getTronNetwork()){
+            $api = TronApi::testNetNilo();
+        }else{
+            $api = TronApi::mainNet();
+        }
+        $kit = new TronKit($api, $credential);
+        $contractAddress = self::getContractAddress($currency);
+        $rate = 1000000;//换算汇率
+        // $balance = self::getBalance(self::getAddressByPrivateKey($fromKey), $currency);  //查询Trc20代币余额
+        $balance = self::getTrc20Balance(self::getAddressByPrivateKey($fromKey));  //查询Trc20代币余额
+        
+        
+        if ($balance < $amount) {
+            throw new \Exception('余额不足');
+        }
+    
+        $trc20 = $kit->Trc20($contractAddress);                      //创建Trc20代币合约实例
+        try {
+            $ret = $trc20->transfer($toAddress, (int) bcmul($amount, $rate));                        //转账Trc20代币
+
+            return (object) [
+                'txid' => $ret->tx->txID,
+                'result' => $ret->result,
+            ];
+        } catch (\Exception $e) {
+            Log::error('Trc20转账错误,fromKey='.$fromKey.'toAddress='.$toAddress.'amount='.$amount);
+            Log::error('Trc20转账错误'.$e->getMessage());
+
+            return false;
+        }
+    }
+
+    /**
+     * 归集账户资金校验
+     * trade_status:
+     * - failed 转账失败
+     * - waiting 转账成功,尚未校验
+     * - closed waiting 之后,脚本查询时,交易验证成功
+     * - aborted waiting 之后,脚本查询时,交易验证失败
+     * @param $fromKey
+     * @param $toAddress
+     * @param $amount
+     * @param $memberId
+     * @param  string  $currency
+     * @param  string  $net
+     * @return bool
+     * @throws \Exception
+     */
+    public function actionFundsGather(
+        $fromKey,
+        $toAddress,
+        $amount,
+        $memberId,
+        $currency = 'TRX',
+        $net = 'TRC20'
+    ) {
+        // $currency = strtoupper($currency);
+        // if ($amount <= 0) {
+        //     throw new \Exception('不能低于 0 trx');
+        // }
+        // if (empty($toAddress)) {
+        //     throw new \Exception('收款地址不能为空');
+        // }
+
+        // $fromAddress = self::getAddressByPrivateKey($fromKey);
+        // $remainAmount = self::getBalance($fromAddress, $currency);
+        // if ($remainAmount < $amount) {
+        //     throw new \Exception('余额不足,归集失败');
+        // }
+        // $min = MinRechargeEnum::getValue($currency);
+        // if ($currency == 'TRX') {
+        //     $ret = self::sendTrx($fromKey, $toAddress, $amount);
+        // } else {
+        //     // 发送
+        //     //校验trx是否足够,不够就充值10燃烧费用进来
+        //     if (self::getBalance($fromAddress, 'TRX') < 10) {
+        //         $withdrawAddressTrxKey = Yii::$app->params['withdraw_address_trx_key'];
+        //         self::sendTrx($withdrawAddressTrxKey, self::getAddressByPrivateKey($fromKey), 10);
+        //     }
+        //     $ret = self::sendTrc20($fromKey, $toAddress, $amount, $currency);
+        // }
+
+        // //少于最低可入款额度,不入账,只做归集
+        // if ($min > $remainAmount) {
+        //     return false;
+        // }
+
+        // try {
+        //     // 记录
+        //     $fundsGather = new Recharge();
+        //     $fundsGather->member_id = $memberId;
+        //     $fundsGather->txid = $ret->txid;
+        //     $fundsGather->amount = $amount;
+        //     $fundsGather->address = $toAddress;
+        //     $fundsGather->created_at = time();
+        //     $fundsGather->currency = $currency;
+        //     $fundsGather->usdt_balance = bcmul($amount, ExchangeRateService::getExchangeRate($currency),
+        //         CommonEnum::DIGITS);
+        //     $fundsGather->is_auto = 1;
+        //     $fundsGather->count = Recharge::find()->where(['member_id' => $memberId])->count() + 1;
+        //     $fundsGather->net = $net;
+        //     $fundsGather->ordernum = StrHelper::createOrdernum();
+
+        //     // 记录「充值请求次数」
+        //     $member = Member::findOne($memberId);
+        //     $member->recharge_request_count = $fundsGather->count;
+        //     $member->save();
+
+        //     if ($ret->result == 1) { // 成功
+        //         $fundsGather->trade_status = 'waiting';
+        //         if ($fundsGather->save()) {
+        //             return true;
+        //         }
+        //     } else { // 失败
+        //         $fundsGather->trade_status = 'failed';
+        //         $fundsGather->save();
+        //     }
+        // } catch (Exception $e) {
+        //     throw new \Exception('转账异常'.$e->getMessage());
+        // }
+
+        return false;
+    }
+
+}

+ 135 - 0
app/Helpers/TronRawSerializer.php

@@ -0,0 +1,135 @@
+<?php
+
+namespace App\Helpers;
+
+use App\Helpers\TronHelper;
+
+class TronRawSerializer
+{
+    public static function serializeRawData(array $rawData): string
+    {
+        $bin = '';
+
+        // timestamp (field number = 1, wire type = 0)
+        $bin .= self::encodeKey(1, 0);
+        $bin .= self::encodeVarint($rawData['timestamp']);
+        
+
+        // ref_block_bytes (2, wire type = 2)
+        $bin .= self::encodeKey(2, 2);
+        $refBlockBytes = hex2bin($rawData['ref_block_bytes']);
+        $bin .= self::encodeLengthDelimited($refBlockBytes);
+        
+
+        // ref_block_hash (3, wire type = 2)
+        $bin .= self::encodeKey(3, 2);
+        $refBlockHash = hex2bin($rawData['ref_block_hash']);
+        $bin .= self::encodeLengthDelimited($refBlockHash);
+        
+
+        // expiration (4, wire type = 0)
+        $bin .= self::encodeKey(4, 0);
+        $bin .= self::encodeVarint($rawData['expiration']);
+        
+
+        // contract (5, repeated embedded message)
+        foreach ($rawData['contract'] as $contract) {
+            $bin .= self::encodeKey(5, 2);
+            $contractBin = self::serializeContract($contract);
+            $bin .= self::encodeLengthDelimited($contractBin);
+        }
+
+        // fee_limit (18, wire type = 0) — optional
+        if (isset($rawData['fee_limit'])) {
+            $bin .= self::encodeKey(18, 0);
+            $bin .= self::encodeVarint($rawData['fee_limit']);
+        }
+
+        return $bin;
+    }
+
+    protected static function serializeContract(array $contract): string
+    {
+        $bin = '';
+
+        // type (1, wire type = 0)
+        $bin .= self::encodeKey(1, 0);
+        $bin .= self::encodeVarint($contract['type']);
+
+        // parameter (2, embedded)
+        $bin .= self::encodeKey(2, 2);
+        $parameterBin = self::serializeParameter($contract['parameter']);
+        $bin .= self::encodeLengthDelimited($parameterBin);
+
+        return $bin;
+    }
+
+    protected static function serializeParameter(array $param): string
+    {
+        $bin = '';
+
+        if (isset($param['type_url'])) {
+            // type_url (1)
+            $bin .= self::encodeKey(1, 2);
+            $bin .= self::encodeLengthDelimited($param['type_url']);
+        }
+
+        if (isset($param['value'])) {
+            // value (2)
+            $bin .= self::encodeKey(2, 2);
+            $valueBin = self::serializeTriggerSmartContract($param['value']);
+            $bin .= self::encodeLengthDelimited($valueBin);
+        }
+
+        return $bin;
+    }
+
+    protected static function serializeTriggerSmartContract(array $value): string
+    {
+        $bin = '';
+
+        // owner_address (1)
+        $bin .= self::encodeKey(1, 2);
+        $bin .= self::encodeLengthDelimited(hex2bin($value['owner_address']));
+        
+
+        // contract_address (2)
+        $bin .= self::encodeKey(2, 2);
+        $bin .= self::encodeLengthDelimited(hex2bin($value['contract_address']));
+ 
+        // call_value (3) — not used in TRC20
+        // data (4)
+        if (isset($value['data'])) {
+            $bin .= self::encodeKey(4, 2);
+            $bin .= self::encodeLengthDelimited(hex2bin($value['data']));
+        }
+
+        return $bin;
+    }
+
+    protected static function encodeKey($fieldNumber, $wireType): string
+    {
+        return self::encodeVarint(($fieldNumber << 3) | $wireType);
+    }
+
+    protected static function encodeVarint($value): string
+    {
+        $value = is_numeric($value) ? (int)$value : 0;
+        $bin = '';
+        while (true) {
+            if (($value & ~0x7F) === 0) {
+                $bin .= chr($value);
+                break;
+            } else {
+                $bin .= chr(($value & 0x7F) | 0x80);
+                $value >>= 7;
+            }
+        }
+        return $bin;
+    }
+
+    protected static function encodeLengthDelimited(string $data): string
+    {
+        return self::encodeVarint(strlen($data)) . $data;
+    }
+}

+ 189 - 0
app/Helpers/helpers.php

@@ -0,0 +1,189 @@
+<?php
+if (!function_exists('get_cache_key')) {
+    function get_cache_key(string $id): string
+    {
+        return 'user_' . $id . '_captcha_code';
+    }
+}
+
+if (!function_exists('get_step_key')) {
+    function get_step_key($chatId)
+    {
+        return "{$chatId}_status";
+    }
+}
+
+if (!function_exists('is_valid_date')) {
+    function is_valid_date($date)
+    {
+        // 使用正则表达式匹配 yyyy-mm-dd 格式的日期
+        return preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) === 1;
+    }
+}
+
+
+if (!function_exists('getUuid')) {
+    function getUuid()
+    {
+        // Generate 16 bytes (128 bits) of random data or use the data passed into the function.
+        $data = random_bytes(16);
+
+        // Set version to 0100
+        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
+        // Set bits 6-7 to 10
+        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
+
+        // Output the 36 character UUID.
+        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
+    }
+}
+
+if (!function_exists('calculate_age')) {
+    function calculate_age($birthDate)
+    {
+        $currentDate = new DateTime();
+        $birthDate = new DateTime($birthDate);
+        $age = $currentDate->diff($birthDate);
+        return $age->y;
+    }
+}
+
+
+if (!function_exists('get_string_translate')) {
+    function get_string_translate(string $key): string
+    {
+        $value = __("messages.translate.{$key}");
+        if ($value == "messages.translate.{$key}") return $key;
+        return $value;
+    }
+}
+
+if (!function_exists('generate_random_string')) {
+    function generate_random_string(int $length = 6): string
+    {
+        $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
+        $randomString = '';
+        for ($i = 0; $i < $length; $i++) {
+            $randomString .= $characters[rand(0, strlen($characters) - 1)];
+        }
+        return $randomString;
+    }
+}
+
+
+if (!function_exists('get_tags')) {
+    function get_tags(int $length = 3): array
+    {
+        $array = ['Reading', 'Traveling', 'Photography', 'Music', 'Painting', 'Yoga', 'Cooking', 'Calligraphy', 'Crafts',
+            'History', 'Drinking', 'Coding', 'Valuing family', 'Skydiving', 'Diving'];
+        $length = $length < 1 ? 1 : ($length > count($array) ? count($array) : $length);
+        $random_keys = array_rand($array, $length);
+        $random_elements = [];
+        foreach ($random_keys as $key) {
+            $random_elements[] = $array[$key];
+        }
+        return $random_elements;
+    }
+}
+
+/**
+ * 辅助函数:十进制转十六进制(支持大数)
+ */
+function bcdechex($dec)
+{
+    $hex = '';
+    while (bccomp($dec, 0) > 0) {
+        $last = bcmod($dec, 16);
+        $hex  = dechex($last) . $hex;
+        $dec  = bcdiv($dec, 16, 0);
+    }
+    return $hex ?: '0';
+}
+
+if (!function_exists('is_multiple_of_eight')) {
+    function is_multiple_of_eight(int $number): bool
+    {
+        if ($number % 8 == 0) {
+            return true;  // 是 8 的倍数
+        } else {
+            return false; // 不是 8 的倍数
+        }
+    }
+}
+
+if (!function_exists('get_invitation_code')) {
+    function get_invitation_code(): string
+    {
+        do {
+            $invitation_code = generate_random_string();
+        } while (\App\Models\User::where('invitation_code', $invitation_code)->first() != null);
+        return $invitation_code;
+    }
+}
+
+if (!function_exists('generate_order_number')) {
+
+    function generate_order_number()
+    {
+        $dateTime = date('YmdHis');
+        $randomNumber = mt_rand(1000, 9999);
+        $orderNumber = $dateTime . $randomNumber;
+        return $orderNumber;
+    }
+}
+
+
+function removeZero($str)
+{
+    if (empty($str)) {
+        return 0;
+    }
+    if (!is_numeric($str)) {
+        return $str;
+    }
+
+    $number = number_format($str, 10, '.', '');
+
+    // 使用 rtrim 移除末尾的零和小数点
+    return rtrim(rtrim($number, '0'), '.');
+}
+
+/**
+ * 唯一订单号
+ * @return string
+ */
+function createOrderNo(): string
+{
+    $str = microtime(true);
+    $arr = explode('.', $str);
+    $decimal = $arr[1];
+
+    return date('YmdHis') . $decimal;
+}
+
+/**
+ * 获取精度
+ * @param $number
+ * @return int
+ */
+function getScale($number): int
+{
+    if (!is_numeric($number)) {
+        return 1;
+    }
+
+    $sub = strrchr($number, ".");
+    if (empty($sub)) {
+        return 1;
+    }
+
+    $scale = strlen(substr($sub, 1));
+    if ($scale == 0) {
+        return 1;
+    }
+
+    return $scale;
+}
+
+
+

+ 113 - 0
app/Http/Controllers/Controller.php

@@ -0,0 +1,113 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Constants\HttpStatus;
+use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
+use Illuminate\Foundation\Bus\DispatchesJobs;
+use Illuminate\Foundation\Validation\ValidatesRequests;
+use Illuminate\Routing\Controller as BaseController;
+use Illuminate\Support\Facades\App;
+
+class Controller extends BaseController
+{
+    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
+
+    /**
+     * @apiDefine header
+     * @apiHeader {String} Authorization "Bearer "+ token
+     *
+     */
+
+    /**
+     * @apiDefine lang
+     * @apiParam (公共参数) {String} [lang=en] 语言
+     * - 允许值:`zh-CN`,`en`
+     */
+
+    /**
+     *
+     */
+    /**
+     * @apiDefine result
+     * @apiSuccess (成功) {Number} code=0 错误代码 0-请求成功 详见  <a href="javascript:;" onclick="toMenu('Error','GetGeterrorcode')">错误代码</a>
+     * @apiSuccess (成功) {Number} timestamp 服务器时间戳
+     * @apiSuccess (成功) {String} msg 错误信息 OK为成功
+     * @apiSuccess (成功) {Array} [data] 数据 若code!=0 则为错误数据,code=101009 该值为验证失败的详情
+     *
+     *
+     */
+
+    /**
+     * @api {get} /getErrorCode 错误代码
+     * @apiGroup Error
+     * @apiSampleRequest off
+     * @apiDescription 下面列出一些常见的错误代码:
+     * | code    | 说明                                                                              |
+     * |---------|-----------------------------------------------------------------------------------|
+     * |-1       |  未知错误,联系开发人员                                                             |
+     * |0        |  OK 请求成功                                                                       |
+     * |101001   |  用户不存在                                                                        |
+     * |101002   |  密码错误                                                                          |
+     * |101003   |  验证码错误                                                                        |
+     * |101004   |  验证码已过期                                                                      |
+     * |101005   |  密码不一致                                                                        |
+     * |101006   |  用户名已存在,请直接登录                                                           |
+     * |101007   |  邮箱已存在,请直接登录                                                             |
+     * |101008   |  用户名错误                                                                       |
+     * |101009   |  参数验证失败,具体错误信息见 data                                                  |
+     * |101010   |  系统错误                                                                         |
+     * |101011   |  没有登录,请检查登录状态                                                           |
+     * |101012   |  禁止收藏自己                                                                     |
+     * |101013   |  先填写基本信息                                                                   |
+     * |101014   |  请求地址不存在,请检查请求地址是否正确                                              |
+     * |101015   |  上传的头像必须是正方形的,如果用户所选的图片不是方形的,请裁剪后上传                   |
+     * |101016   |  没有匹配到合适的对象                                                              |
+     * |101017   |  可收藏数达到最大值,完善资料可获取更多数量                                           |
+     * |101018   |  发送失败                                                                        |
+     * |101019   |  手机号码不正确                                                                    |
+     * |101020   |  帖子不存在                                                                        |
+     * |101021   |  文件上传错误                                                                      |
+     * |101022   |  邀请码错误                                                                       |
+     * |101023  |  用户已在其他设备登录                                                                |
+     * |101024   |  剩余抽奖次数不足                                                                   |
+     * |101025   |  地址数量最多10条                                                                   |
+     * |101026   | post请求错误 |
+     * |101027   | IM 错误 |
+     * |101028   | 手机号已存在或已绑定其他账号,请直接登录或绑定其它手机号 |
+     * |101029   | 谷歌登录错误 |
+     * |101030   | 聊天余额不足 |
+     * |101031   | 钱包余额不足 |
+     * |101032   | Facebook 错误 |
+     * |101033   | 资料验证失败,请检查当前是否是待验证状态 |
+     *
+     * @apiVersion 1.0.0
+     */
+
+    protected function success($data = [], $msg = '')
+    {
+        return response()->json([
+            'code' => HttpStatus::OK,
+            'timestamp' => time(),
+            'msg' => __('messages.' . HttpStatus::OK),
+            'data' => $data
+        ]);
+    }
+
+    protected function error($code, string $msg = '', $data = [])
+    {
+        App::setLocale(isset($_REQUEST['lang']) && $_REQUEST['lang'] == 'en' ? 'en' : 'zh-CN');
+        $code = intval($code);
+        if ($code === 0) $code = -1;
+        $m = __('messages.' . $code);
+        if ($msg) $m .= ":{$msg}";
+        if ($code === -3) $m = $msg;
+        return response()->json([
+            'code' => $code,
+            'timestamp' => time(),
+            'msg' => $m,
+            'data' => $data
+        ]);
+    }
+
+}

+ 291 - 0
app/Http/Controllers/admin/Admin.php

@@ -0,0 +1,291 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Http\Controllers\Controller;
+use App\Services\JwtService;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Validator;
+use App\Models\Admin as AdminModel;
+use Exception;
+use Illuminate\Validation\ValidationException;
+use function Symfony\Component\HttpFoundation\Session\Storage\Handler\commit;
+use App\Services\AdminService;   
+use Illuminate\Validation\Rule;          
+
+/**
+ * @apiDefine result
+ * @apiSuccess (成功) {Number} code=0 错误代码 0-请求成功 详见  <a href="javascript:;" onclick="toMenu('Error','GetGeterrorcode')">错误代码</a>
+ * @apiSuccess (成功) {Number} timestamp 服务器时间戳
+ * @apiSuccess (成功) {String} msg 错误信息 OK为成功
+ * @apiSuccess (成功) {Array} [data] 数据 若code!=0 则为错误数据,code=101009 该值为验证失败的详情
+ *
+ *
+ */
+/**
+ * @apiDefine header
+ * @apiHeader {String} Authorization "Bearer "+ token
+ *
+ */
+
+/**
+ * @api {get} /getErrorCode 错误代码
+ * @apiGroup Error
+ * @apiSampleRequest off
+ * @apiDescription 下面列出一些常见的错误代码:
+ * | code    | 说明                                                                              |
+ * |---------|-----------------------------------------------------------------------------------|
+ * |-1       |  未知错误,联系开发人员                                                             |
+ * |0        |  OK 请求成功                                                                       |
+ * |101001   |  用户不存在                                                                        |
+ * |101002   |  密码错误                                                                          |
+ * |101003   |  验证码错误                                                                        |
+ * |101004   |  验证码已过期                                                                      |
+ * |101005   |  密码不一致                                                                        |
+ * |101006   |  用户名已存在,请直接登录                                                           |
+ * |101007   |  邮箱已存在,请直接登录                                                             |
+ * |101008   |  用户名错误                                                                       |
+ * |101009   |  参数验证失败,具体错误信息见 data                                                  |
+ * |101010   |  系统错误                                                                         |
+ * |101011   |  没有登录,请检查登录状态                                                           |
+ * |101012   |  禁止收藏自己                                                                     |
+ * |101013   |  先填写基本信息                                                                   |
+ * |101014   |  请求地址不存在,请检查请求地址是否正确                                              |
+ * |101015   |  上传的头像必须是正方形的,如果用户所选的图片不是方形的,请裁剪后上传                   |
+ * |101016   |  没有匹配到合适的对象                                                              |
+ * |101017   |  可收藏数达到最大值,完善资料可获取更多数量                                           |
+ * |101018   |  发送失败                                                                        |
+ * |101019   |  手机号码不正确                                                                    |
+ * |101020   |  帖子不存在                                                                        |
+ * |101021   |  文件上传错误                                                                      |
+ * |101022   |  邀请码错误                                                                       |
+ * |101023  |  用户已在其他设备登录                                                                |
+ * |101024   |  剩余抽奖次数不足                                                                   |
+ * |101025   |  地址数量最多10条                                                                   |
+ * |101026   | post请求错误 |
+ * |101027   | IM 错误 |
+ * |101028   | 手机号已存在或已绑定其他账号,请直接登录或绑定其它手机号 |
+ * |101029   | 谷歌登录错误 |
+ * |101030   | 聊天余额不足 |
+ * |101031   | 钱包余额不足 |
+ * |101032   | Facebook 错误 |
+ * |101033   | 资料验证失败,请检查当前是否是待验证状态 |
+ *
+ * @apiVersion 1.0.0
+ */
+class Admin extends Controller
+{
+    protected $jwtService;
+
+    public function __construct(JwtService $jwtService)
+    {
+        $this->jwtService = $jwtService;
+    }
+
+    /**
+     * @api {post} /admin/setPassword 修改密码
+     * @apiGroup 管理员
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {string} oldPassword 旧密码
+     * @apiParam {string} password 新密码
+     * @apiParam {string} password_confirmation 确认密码
+     *
+     */
+    public function setPassword()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'oldPassword' => ['required', 'string', 'min:1'],
+                'password' => ['required', 'string', 'min:8', 'max:20', 'confirmed'],
+            ]);
+
+            $user = request()->user;
+            $oldPassword = request()->input('oldPassword', '');
+            $password = request()->input('password', '');
+            if (!password_verify($oldPassword, $user->password)) {
+                throw new Exception('', HttpStatus::PASSWORDS_ERROR);
+            }
+            $user->password = password_hash($password, PASSWORD_DEFAULT);
+            $user->save();
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success();
+    }
+
+    public function logout()
+    {
+        Auth::logout();
+        session()->regenerateToken();
+        return $this->success();
+    }
+
+    /**
+     * @api {post} /admin/login 登录
+     * @apiGroup 管理员
+     * @apiUse result
+     * @apiVersion 1.0.0
+     * @apiParam {string} username
+     * @apiParam {string} password
+     *
+     * @apiSuccess (成功) data
+     * @apiSuccess (成功) data.token
+     */
+    function login()
+    {
+        try {
+            $username = request()->input('username');
+            $password = request()->input('password');
+            $user = AdminModel::login($username, $password);
+            $token = $this->jwtService->generateToken($user);
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()));
+        }
+        $data = [
+            'token' => "Bearer $token",
+            'userInfo' => $user
+        ];
+        return $this->success($data);
+    }
+
+    function test()
+    {
+        return $this->success('ok');
+    }
+
+    /**
+     * @api {get} /admin/index 人员列表
+     * @apiGroup 管理员
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} [page=1]
+     * @apiParam {int} [limit=10]
+     * @apiParam {string} [username] 账号
+     * @apiParam {string} [nickname] 昵称
+     *
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.total 数量
+     * @apiSuccess (data) {Object[]} data.data 列表
+     * @apiSuccess (data) {int} data.data.id
+     * @apiSuccess (data) {string} data.data.username 账号
+     * @apiSuccess (data) {string} data.data.nickname 昵称
+     * @apiSuccess (data) {array} data.data.roles_ids 账号的角色
+     * @apiSuccess (data) {array} data.data.roles_names 账号的角色名称
+     * @apiSuccess (data) {string} data.data.updated_at
+     * @apiSuccess (data) {string} data.data.created_at
+     */
+    public function index()
+    {
+        // try {
+            request()->validate([
+                'username' => ['nullable', 'string'],
+                'nickname' => ['nullable', 'string'],
+            ]);
+            $search = request()->all();
+            $result = AdminService::paginate($search);
+        // } catch (ValidationException $e) {
+        //     return $this->error(HttpStatus::VALIDATION_FAILED, '', $e->errors());
+        // } catch (Exception $e) {
+        //     return $this->error(intval($e->getCode()));
+        // }
+        return $this->success($result);
+    }
+
+
+    /**
+     * @api {post} /admin/submit 修改账号
+     * @apiGroup 管理员
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} id 角色ID
+     * @apiParam {string} username 账号
+     * @apiParam {string} nickname 昵称
+     * @apiParam {string} password 密码
+     * @apiParam {array} roles_ids 账号角色
+     */
+    public function store()
+    {
+        // try {
+            $params = request()->all();
+            if(isset($params['id']) && $params['id'] == 1){
+                return $this->error(0, '超级管理员禁止操作');
+            }
+            $validator = [
+                 'username' => 'required|string|max:50|alpha_dash|unique:admin,username',
+                 'nickname' => 'required|string|max:100',
+                 'password' => ['nullable', 'string', 'min:6', 'max:20'],
+                // 'display_name' => 'nullable|string|max:100',
+                // 'description' => 'nullable|string',
+            ];
+            if(isset($params['id']) && !empty($params['id'])){
+                $validator['username'] = [
+                                'required',
+                                'string',
+                                'max:50',
+                                'alpha_dash',
+                                Rule::unique('admin', 'username')->ignore($params['id']), // 忽略当前 ID
+                                ];
+            }else{
+
+            }
+            
+
+            request()->validate($validator);
+
+            $ret = AdminService::submit($params);
+            if ($ret['code'] == AdminService::NOT) {
+                return $this->error($ret['code'], $ret['msg']);
+            }
+        // } catch (ValidationException $e) {
+        //     return $this->error(HttpStatus::VALIDATION_FAILED, '', $e->errors());
+        // } catch (Exception $e) {
+        //     return $this->error(intval($e->getCode()));
+        // }
+        return $this->success([], $ret['msg']);
+
+    }
+
+    /**
+     * @api {post} /admin/delete 删除账号
+     * @apiGroup 管理员
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} id 角色ID
+     */
+    public function destroy()
+    {
+        $id = request()->post('id');
+        if($id == 1){
+            return $this->error(0, '超级管理员禁止操作');
+        }
+        // 示例:通过 ID 删除菜单
+        $info = AdminService::findOne(['id' => $id]);
+        if (!$info) {
+            return $this->error(0, '账号不存在');
+        }
+
+        $info->delete();
+
+        return $this->success([], '删除成功');
+    }
+}

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

@@ -0,0 +1,58 @@
+<?php
+
+
+namespace App\Http\Controllers\admin;
+
+
+use App\Constants\HttpStatus;
+use App\Http\Controllers\Controller;
+use App\Services\BalanceLogService;
+use Illuminate\Validation\ValidationException;
+use Exception;
+
+class Balance extends Controller
+{
+
+    /**
+     * @api {get} /admin/balance/log 钱包记录
+     * @apiGroup 钱包相关
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} [page=1]
+     * @apiParam {int} [limit=10]
+     * @apiParam {string} [member_id] tg会员ID
+     *
+     *
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.total 数量
+     * @apiSuccess (data) {Object[]} data.data 列表
+     * @apiSuccess (data) {int} data.data.id
+     * @apiSuccess (data) {string} data.data.room_id 房间号,如果是结算
+     * @apiSuccess (data) {string} data.data.member_id  tg会员id
+     * @apiSuccess (data) {string} data.data.amount 变动金额
+     * @apiSuccess (data) {string} data.data.before_balance 变动前余额
+     * @apiSuccess (data) {string} data.data.after_balance 变动后余额
+     * @apiSuccess (data) {string} data.data.change_type 变动类型,如 "充值"、"提现"、"结算" 等
+     * @apiSuccess (data) {int} data.data.related_id 相关ID
+     * @apiSuccess (data) {string} data.data.updated_at
+     * @apiSuccess (data) {string} data.data.created_at
+     */
+    public function log()
+    {
+        try {
+            request()->validate([
+                'member_id' => ['nullable', 'string', 'min:1']
+            ]);
+            $search = request()->all();
+            $result = BalanceLogService::paginate($search);
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success($result);
+    }
+}

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

@@ -0,0 +1,368 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Constants\Util;
+use App\Http\Controllers\Controller;
+use App\Models\Config as ConfigModel;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\ValidationException;
+use Exception;
+use Telegram\Bot\Api;
+use Telegram\Bot\Exceptions\TelegramSDKException;
+use Telegram\Bot\FileUpload\InputFile;
+
+class Config extends Controller
+{
+    /**
+     * @api {get} /admin/config/get 获取指定配置
+     * @apiGroup 配置
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {string} field 配置项
+     * - base_score 房间底分数组
+     * - brokerage 抽佣比例
+     * - service_charge 提现手续费
+     * - service_account 客服账号
+     * - receiving_address 手动收款 的地址
+     * - receiving_type 收款方式 1-自动 2-手动
+     * - channel_message 频道消息
+     *
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int[]} [data.base_score] 房间底分数组
+     * @apiSuccess (data) {float} [data.brokerage] 抽佣比例
+     * @apiSuccess (data) {int} [data.service_charge] 提现手续费
+     * @apiSuccess (data) {string} [data.service_account] 客服账号
+     * @apiSuccess (data) {string} [data.receiving_address] 手动收款 的地址
+     * @apiSuccess (data) {int} [data.receiving_type] 收款方式 1-自动 2-手动
+     * @apiSuccess (data) {Object} [data.channel_message] 频道消息
+     * @apiSuccess (data) {string} data.channel_message.chatId 频道账号或频道ID
+     * @apiSuccess (data) {string} data.channel_message.image 要发送频道消息 的图片URL
+     * @apiSuccess (data) {string} data.channel_message.text 要发送频道消息文 的本内容
+     * @apiSuccess (data) {array} data.channel_message.button 要发送频道消息 的内联按钮,具体结构见下方
+     * @apiSuccessExample {js} 内联按钮结构
+     * //button 的数据格式如下
+     * [
+     *   [      //第一行
+     *     {    //第一行按钮 的第一个按钮
+     *       "text": "百度",  //按钮文字
+     *       "url": "https://baidu.com"   //按钮跳转的链接
+     *     },
+     *     {    //第一行按钮 的第二个按钮
+     *       "text": "百度",
+     *       "url": "https://baidu.com"
+     *     }
+     *     //更多按钮...
+     *   ],
+     *   [    //第二行
+     *     {
+     *       "text": "百度",
+     *       "url": "https://baidu.com"
+     *     }
+     *   ]
+     *   //更多行...
+     * ]
+     *
+     */
+    public function get()
+    {
+        try {
+            request()->validate([
+                'field' => ['required', 'string', 'min:1'],
+            ]);
+            $field = request()->input('field');
+            $config = ConfigModel::where('field', $field)->first();
+            $res = [];
+            if ($config) {
+                $val = $config->val;
+                switch ($field) {
+                    case "channel_message":
+                    case "base_score":
+                        $val = json_decode($config->val);
+                        break;
+                    case "brokerage":
+                    case "service_charge":
+                        $val = floatval($config->val);
+                        break;
+                }
+                $res[$field] = $val;
+            }
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success($res);
+    }
+
+
+    /**
+     * @api {get} /admin/config/getAll 获取所有配置
+     * @apiGroup 配置
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     */
+    public function getAll()
+    {
+        $list = ConfigModel::where('id', '>', 0)->get();
+        $arr = [];
+        foreach ($list as $item) {
+            $val = $item['val'];
+            switch ($item['field']) {
+                case "channel_message":
+                case "base_score":
+                    $val = json_decode($item['val'], true);
+                    break;
+                case "brokerage":
+                case "service_charge":
+                    $val = floatval($item['val']);
+                    break;
+            }
+            $arr[$item['field']] = $val;
+        }
+        return $this->success($arr);
+    }
+
+    /**
+     * @api {post} /admin/config/sendChannelMessage 发送频道消息
+     * @apiGroup 配置
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     * @apiDescription 该接口会保存配置,并发送频道消息; 创建频道之后需要将 bot 拉进频道内,然后将 bot 设置为管理员
+     *
+     *
+     * @apiParam {String} chatId 频道的username - 创建频道时候填写的 username
+     * @apiParam {String} type 发送的类型 - image:图片 - video:视频
+     * @apiParam {String} image 要发送的图片
+     * @apiParam {String} text 要发送的文字
+     * @apiParam {Array} button 消息中的按钮 具体结构请看示例代码
+     * @apiParam {String} video 视频
+     * @apiParam {String} video_caption 视频文案
+     * @apiParam {Boolean} [isSend=true] 是否发送
+     * @apiParam {Boolean} [isTop=true] 是否置顶
+     * @apiExample {js} button 示例
+     * //button 的数据格式如下
+     * [
+     *   [      //第一行
+     *     {    //第一行按钮 的第一个按钮
+     *       "text": "百度",  //按钮文字
+     *       "url": "https://baidu.com"   //按钮跳转的链接
+     *     },
+     *     {    //第一行按钮 的第二个按钮
+     *       "text": "百度",
+     *       "url": "https://baidu.com"
+     *     }
+     *     //更多按钮...
+     *   ],
+     *   [    //第二行
+     *     {
+     *       "text": "百度",
+     *       "url": "https://baidu.com"
+     *     }
+     *   ]
+     *   //更多行...
+     * ]
+     */
+    public function sendChannelMessage()
+    {
+        set_time_limit(0);
+        DB::beginTransaction();
+        try {
+            $type = request()->input('type', 'image');
+            if ($type == 'image') {
+                request()->validate([
+                    'chatId' => ['required', 'string', 'min:1'],
+                    'image' => ['required', 'url'],
+                    'text' => ['nullable', 'string'],
+                    'button' => ['required', 'array'],
+                    'button.*' => ['array'],
+                    'button.*.*.text' => ['required', 'string'],
+                    'button.*.*.url' => ['required', 'url'],
+                    'isSend' => ['nullable', 'boolean'],
+                    'isTop' => ['nullable', 'boolean'],
+                ]);
+
+
+            } else {
+                request()->validate([
+                    'chatId' => ['required', 'string', 'min:1'],
+                    'video' => ['required','url'],
+                    'video_caption' => ['nullable', 'string'],
+                    'isSend' => ['nullable', 'boolean'],
+                    'isTop' => ['nullable', 'boolean'],
+                ]);
+            }
+            $chatId = request()->input('chatId');
+            $image = request()->input('image');
+            $button = request()->input('button');
+            $text = request()->input('text');
+            $isSend = request()->input('isSend', true);
+            $isTop = request()->input('isTop', true);
+
+            $video = request()->input('video');
+            $video_caption = request()->input('video_caption');
+            ConfigModel::where('field', 'channel_message')
+                ->update([
+                    'val' => json_encode([
+                        'chatId' => $chatId,
+                        'image' => $image,
+                        'video' => $video,
+                        'video_caption' => $video_caption,
+                        'text' => $text,
+                        'button' => $button
+                    ])
+                ]);
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+
+        if ($isSend) {
+            try {
+                $config = ConfigModel::where('field', 'channel_message')
+                    ->first()->val;
+                $config = json_decode($config, true);
+                $telegram = new Api(config('services.telegram.token'));
+                if ($type == 'image') {
+                    // 发送图片消息
+                    $response = $telegram->sendPhoto([
+                        'chat_id' => "@{$config['chatId']}",
+                        'photo' => InputFile::create($config['image']),
+                        'caption' => $config['text'],
+                        'protect_content' => false,
+                        'reply_markup' => json_encode(['inline_keyboard' => $config['button']])
+                    ]);
+                } else {
+                    // 发送视频消息
+                    $response = $telegram->sendVideo([
+                        'chat_id' => "@{$config['chatId']}",
+                        'video' => InputFile::create($config['video']),
+                        'caption' => $config['video_caption'],
+                        'protect_content' => false
+                        // 'reply_markup' => json_encode(['inline_keyboard' => $config['button']])
+                    ]);
+                }
+
+
+                if ($isTop) {
+                    // 获取消息ID
+                    $messageId = $response->get('message_id');
+                    // 置顶消息
+                    $telegram->pinChatMessage([
+                        'chat_id' => "@{$config['chatId']}",
+                        'message_id' => $messageId
+                    ]);
+                }
+            } catch (TelegramSDKException $e) {
+                return $this->error(HttpStatus::CUSTOM_ERROR, '保存成功,发送失败');
+            } catch (Exception $e) {
+                return $this->error(intval($e->getCode()), '保存成功,发送失败');
+            }
+        }
+
+
+        return $this->success();
+    }
+
+    // public function sendChannelVideo()
+    // {
+    //     $chatId = request()->input('chatId');
+    //     $video = request()->input('video');
+    //     // $config = ConfigModel::where('field', 'channel_message')
+    //     //             ->first()->val;
+    //     // $config = json_decode($config, true);
+    //     $telegram = new Api(config('services.telegram.token'));
+    //     // 发送图片消息
+    //     $response = $telegram->sendVideo([
+    //         'chat_id' => "@{$chatId}",
+    //         'caption' => '这是一个视频消息',
+    //         'video' => InputFile::create($video),
+    //         'protect_content' => false,
+    //     ]);
+
+    //     // 获取消息ID
+    //     $messageId = $response->get('message_id');
+    //     // 获取消息ID
+    //     $messageId = $response->get('message_id');
+    //     // 置顶消息
+    //     $telegram->pinChatMessage([
+    //         'chat_id' => "@{$chatId}",
+    //         'message_id' => $messageId
+    //     ]);
+    //     return $this->success($messageId);
+    // }
+
+
+    /**
+     * @api {post} /admin/config/set 修改配置
+     * @apiGroup 配置
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int[]} base_score 房间底分数组
+     * @apiParam {string} brokerage 抽佣比例
+     * @apiParam {string} service_charge 提现手续费
+     * @apiParam {string} service_account 客服账号
+     * @apiParam {string} receiving_address 充值收款地址
+     * @apiParam {string} receiving_type 收款方式 1-自动 2-手动
+     *
+     */
+    public function set()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'base_score' => ['required', 'array', 'min:1', 'max:30'],
+                'base_score.*' => ['integer', 'min:1'],
+                'brokerage' => ['required', 'numeric', 'min:0.01', 'max:1', 'regex:/^\d*(\.\d{1,2})?$/'],
+                'service_charge' => ['required', 'integer', 'min:1'],
+                'service_account' => ['required', 'string', 'min:1'],
+                'receiving_address' => ['required', 'string', 'min:34'],
+                'receiving_type' => ['required', 'integer', 'in:1,2']
+            ]);
+            $baseScore = request()->input('base_score');
+            sort($baseScore);
+            $baseScore = array_unique($baseScore);
+            ConfigModel::where('field', 'base_score')
+                ->update(['val' => json_encode($baseScore)]);
+
+            $val = request()->input('brokerage');
+            ConfigModel::where('field', 'brokerage')
+                ->update(['val' => $val]);
+            $val = request()->input('service_charge');
+            ConfigModel::where('field', 'service_charge')
+                ->update(['val' => $val]);
+
+            $val = request()->input('service_account');
+            ConfigModel::where('field', 'service_account')
+                ->update(['val' => $val]);
+
+            $val = request()->input('receiving_address');
+            ConfigModel::where('field', 'receiving_address')
+                ->update(['val' => $val]);
+
+            $val = request()->input('receiving_type');
+            ConfigModel::where('field', 'receiving_type')
+                ->update(['val' => $val]);
+
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success();
+    }
+}

+ 128 - 0
app/Http/Controllers/admin/Game.php

@@ -0,0 +1,128 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Http\Controllers\Controller;
+use App\Services\GameService;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\ValidationException;
+use Exception;
+use App\Models\Game as GameModel;
+
+class Game extends Controller
+{
+
+    /**
+     * @api {get} /admin/game 游戏列表
+     * @apiGroup 游戏管理
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} [page=1]
+     * @apiParam {int} [limit=10]
+     * @apiParam {string} [game_name] 游戏名称
+     *
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.total 数量
+     * @apiSuccess (data) {Object[]} data.data 列表
+     * @apiSuccess (data) {int} data.data.id
+     * @apiSuccess (data) {int} data.data.game_name 游戏名称
+     * @apiSuccess (data) {string} data.data.updated_at
+     * @apiSuccess (data) {string} data.data.created_at
+     */
+    function index()
+    {
+        try {
+            request()->validate([
+                'game_name' => ['nullable', 'string'],
+            ]);
+            $search = request()->all();
+            $result = GameService::paginate($search);
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success($result);
+    }
+
+    /**
+     * @api {post} /admin/game/update 修改
+     * @apiDescription 更新或者新增都用这个接口
+     * @apiGroup 游戏管理
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} id 要更新的ID
+     * - 如果是新增,则id=0
+     * @apiParam {string} game_name 游戏名称
+     */
+    public function update()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'id' => ['required', 'integer', 'min:0', 'max:99999999'],
+                'game_name' => ['required', 'string', 'min:1', 'max:32'],
+            ]);
+            $id = request()->input('id', 0);
+            $gameName = request()->input('game_name');
+            $gameName = trim($gameName);
+            if (GameModel::where('game_name', $gameName)->first()) {
+                throw new Exception('游戏名称已存在', HttpStatus::CUSTOM_ERROR);
+            }
+
+
+            GameModel::updateOrCreate(
+                ['id' => $id],
+                ['game_name' => $gameName]
+            );
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success();
+    }
+
+
+    /**
+     * @api {post} /admin/game/delete 删除
+     * @apiGroup 游戏管理
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} id 要删除的ID
+     *
+     */
+    function destroy()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'id' => ['required', 'integer', 'min:1', 'max:99999999'],
+            ]);
+            $id = request()->input('id', 0);
+            $game = GameModel::where('id', $id)->first();
+            if (!$game) throw new Exception("数据不存在", HttpStatus::CUSTOM_ERROR);
+            $game->delete();
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success();
+    }
+
+
+}

+ 232 - 0
app/Http/Controllers/admin/Menu.php

@@ -0,0 +1,232 @@
+<?php
+
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Http\Controllers\Controller;
+use App\Services\MenuService;
+use Exception;
+
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\ValidationException;
+
+class Menu extends Controller
+{
+
+
+    /**
+     * @api {get} /admin/menu 菜单列表
+     * @apiGroup 菜单管理
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} [page=1]
+     * @apiParam {int} [limit=10]
+     * @apiParam {string} [title] 菜单名称
+     * @apiParam {int} [parent_id] 上级菜单
+     * @apiParam {int} [type] 类型 1-菜单 2-按钮
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.total 数量
+     * @apiSuccess (data) {Object[]} data.data 列表
+     * @apiSuccess (data) {int} data.data.id
+     * @apiSuccess (data) {int} data.data.parent_id 上级菜单
+     * @apiSuccess (data) {string} data.data.title 菜单名称
+     * @apiSuccess (data) {string} data.data.uri 路由地址
+     * @apiSuccess (data) {int} data.data.sort 排序标识
+     * @apiSuccess (data) {string} data.data.updated_at
+     * @apiSuccess (data) {string} data.data.created_at
+     */
+    public function index()
+    {
+        try {
+            request()->validate([
+                'title' => ['nullable', 'string'],
+                'permission_name' => ['nullable', 'string'],
+            ]);
+            $search = request()->all();
+            $result = MenuService::paginate($search);
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::VALIDATION_FAILED, '', $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success($result);
+    }
+
+    /**
+     * @api {post} /admin/menu/submit 修改菜单
+     * @apiGroup 菜单管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} id 菜单ID 
+     * @apiParam {string} title 菜单名称
+     * @apiParam {string} [uri] 菜单链接地址/路由
+     * @apiParam {string} [parent_id] 父级菜单ID
+     * @apiParam {string} [sort] 排序值(越小越靠前)
+     * @apiParam {string} [status] 状态 1-显示 2-隐藏
+     * @apiParam {string} [type] 类型 1-菜单 2-按钮
+     */
+    public function store()
+    {
+        try {
+            $params = request()->all();
+
+            $validator = [
+                'title' => 'required|string|max:100',
+                'uri' => 'nullable|string|max:255',
+                // 'icon'             => 'nullable|string',
+                // 'permission_name'  => 'nullable|string|max:100',
+                'parent_id' => 'nullable|integer',
+                'status' => 'required|nullable|integer',
+                'type' => 'required|nullable|integer',
+                'sort' => 'required|nullable|integer',
+            ];
+
+            request()->validate($validator);
+            $ret = MenuService::submit($params);
+            if ($ret['code'] == MenuService::NOT) {
+                return $this->error($ret['code'], $ret['msg']);
+            }
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success([], $ret['msg']);
+
+    }
+
+    /**
+     * @api {post} /admin/menu/delete 删除
+     * @apiGroup 菜单管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} id 菜单ID
+     */
+    public function destroy()
+    {
+        $id = request()->post('id');
+        // 示例:通过 ID 删除菜单
+        $info = MenuService::findOne(['id' => $id]);
+        if (!$info) {
+            return $this->error(0, '菜单不存在');
+        }
+
+        $info->delete();
+
+        return $this->success([], '删除成功');
+    }
+
+    /**
+     * @api {get} /admin/menu/all 全部菜单
+     * @apiGroup 菜单管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiSuccess (data) {Object[]} data
+     * @apiSuccess (data) {int} data.id 菜单ID
+     * @apiSuccess (data) {int} data.parent_id 父级菜单ID
+     * @apiSuccess (data) {string} data.title 菜单名称
+     * @apiSuccess (data) {string} data.icon 菜单图标
+     * @apiSuccess (data) {string} data.uri 菜单链接地址/路由
+     * @apiSuccess (data) {string} data.type 类型 1-菜单 2-按钮
+     * @apiSuccess (data) {string} data.status 状态 1-显示 2-隐藏
+     * @apiSuccess (data) {int} data.sort 排序值(越小越靠前)
+     *
+     */
+    public function all()
+    {
+        $search = request()->all();
+        $result = MenuService::findAll($search);
+        return $this->success($result);
+    }
+
+
+    /**
+     * @api {get} /admin/menu/mineMenu 我的菜单
+     * @apiGroup 菜单管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiSuccess (data) {Object[]} data
+     * @apiSuccess (data) {int} data.id 菜单ID
+     * @apiSuccess (data) {int} data.parent_id 父级菜单ID
+     * @apiSuccess (data) {string} data.title 菜单名称
+     * @apiSuccess (data) {string} data.icon 菜单图标
+     * @apiSuccess (data) {string} data.uri 菜单链接地址/路由
+     * @apiSuccess (data) {string} data.type 类型 1-菜单 2-按钮
+     * @apiSuccess (data) {string} data.status 状态 1-显示 2-隐藏
+     * @apiSuccess (data) {int} data.sort 排序值(越小越靠前)
+     * @apiSuccess (data) {array} data.children 下级菜单
+     *
+     */
+    public function mineMenu()
+    {
+        $userId = request()->user->id;
+        $result = MenuService::getUserMenu($userId);
+        return $this->success($result);
+    }
+
+    /**
+     * @api {get} /admin/menu/tree 菜单按钮树
+     * @apiGroup 菜单管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiSuccess (data) {Object[]} data
+     * @apiSuccess (data) {int} data.id 菜单ID
+     * @apiSuccess (data) {int} data.parent_id 父级菜单ID
+     * @apiSuccess (data) {string} data.title 菜单名称
+     * @apiSuccess (data) {string} data.icon 菜单图标
+     * @apiSuccess (data) {string} data.uri 菜单链接地址/路由
+     * @apiSuccess (data) {string} data.type 类型 1-菜单 2-按钮
+     * @apiSuccess (data) {string} data.status 状态 1-显示 2-隐藏
+     * @apiSuccess (data) {int} data.sort 排序值(越小越靠前)
+     * @apiSuccess (data) {array} data.children 下级菜单
+     *
+     */
+    public function tree()
+    {
+        $result = MenuService::getTree();
+        return $this->success($result);
+    }
+
+    /**
+     * @api {post} /admin/menu/check 接口权限校验
+     * @apiGroup 菜单管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} uri 接口地址 需要带admin 例子:admin/menu
+     * @apiSuccess (data) {Object[]} data
+     *
+     */
+    public function check()
+    {
+        $userId = request()->user->id;
+        $uri = request()->post('uri','');
+        $result = MenuService::checkMenu($userId,$uri);
+        if($result){
+            return $this->success($result, '有权限访问');
+        }else{
+            return $this->error(99,'没有权限访问',$result);
+        }
+    }
+
+}

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

@@ -0,0 +1,146 @@
+<?php
+
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Http\Controllers\Controller;
+use App\Services\RoleService;
+use Exception;
+
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\ValidationException;
+
+class Role extends Controller
+{
+
+
+    /**
+     * @api {get} /admin/role 角色列表
+     * @apiGroup 角色管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} [page=1]
+     * @apiParam {int} [limit=10]
+     * @apiParam {string} [display_name] 角色名称
+     *
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.total 数量
+     * @apiSuccess (data) {Object[]} data.data 列表
+     * @apiSuccess (data) {int} data.data.id
+     * @apiSuccess (data) {string} data.data.display_name 角色名称(显示用)
+     * @apiSuccess (data) {string} data.data.description 角色描述
+     * @apiSuccess (data) {array} data.data.menus_ids 角色拥有的菜单
+     * @apiSuccess (data) {string} data.data.updated_at
+     * @apiSuccess (data) {string} data.data.created_at
+     */
+    public function index()
+    {
+        try {
+            request()->validate([
+                'title' => ['nullable', 'string'],
+                'permission_name' => ['nullable', 'string'],
+            ]);
+            $search = request()->all();
+            $result = RoleService::paginate($search);
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::VALIDATION_FAILED, '', $e->errors());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success($result);
+    }
+
+
+    /**
+     * @api {post} /admin/role/submit 修改角色
+     * @apiGroup 角色管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} id 角色ID
+     * @apiParam {string} display_name 角色名称(显示用)
+     * @apiParam {string} description 角色描述
+     * @apiParam {array} [menus_ids] 角色菜单
+     */
+    public function store()
+    {
+        // try {
+            $params = request()->all();
+
+            $validator = [
+                // 'name' => 'required|string|max:50|alpha_dash',
+                // 'name' => 'required|string|max:50|alpha_dash|unique:roles,name',
+                'display_name' => 'nullable|string|max:100',
+                'description' => 'nullable|string',
+            ];
+
+            request()->validate($validator);
+
+            $ret = RoleService::submit($params);
+            if ($ret['code'] == RoleService::NOT) {
+                return $this->error($ret['code'], $ret['msg']);
+            }
+        // } catch (ValidationException $e) {
+        //     return $this->error(HttpStatus::VALIDATION_FAILED, '', $e->errors());
+        // } catch (Exception $e) {
+        //     return $this->error(intval($e->getCode()));
+        // }
+        return $this->success([], $ret['msg']);
+
+    }
+
+    /**
+     * @api {post} /admin/role/delete 删除角色
+     * @apiGroup 角色管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} id 角色ID
+     */
+    public function destroy()
+    {
+        $id = request()->post('id');
+        // 示例:通过 ID 删除菜单
+        $info = RoleService::findOne(['id' => $id]);
+        if (!$info) {
+            return $this->error(0, '角色不存在');
+        }
+
+        $info->delete();
+
+        return $this->success([], '删除成功');
+    }
+
+    /**
+     * @api {get} /admin/role/all 全部角色
+     * @apiGroup 角色管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiSuccess (data) {Object[]} data 列表
+     * @apiSuccess (data) {int} data.id
+     * @apiSuccess (data) {string} data.display_name 角色名称(显示用)
+     * @apiSuccess (data) {string} data.description 角色描述
+     * @apiSuccess (data) {array} data.data.menus_ids 角色拥有的菜单
+     * @apiSuccess (data) {string} data.updated_at
+     * @apiSuccess (data) {string} data.created_at
+     *
+     */
+    public function all()
+    {
+        $search = request()->all();
+        $result = RoleService::findAll($search);
+        return $this->success($result);
+    }
+
+}

+ 216 - 0
app/Http/Controllers/admin/Room.php

@@ -0,0 +1,216 @@
+<?php
+
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Constants\Util;
+use App\Http\Controllers\Controller;
+use App\Models\Config;
+use App\Models\RoomUser;
+use App\Services\RoomService;
+use App\Services\SettlementService;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\ValidationException;
+use Exception;
+use Telegram\Bot\Api;
+
+class Room extends Controller
+{
+    /**
+     * @api {post} /admin/room/completed 结算
+     * @apiGroup 房间管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     * @apiParam {string} room_id 房间号
+     */
+    public function completed()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'room_id' => ['required', 'string', 'min:1'],
+            ]);
+            $roomId = request()->input('room_id');
+            $list = RoomUser::where('status', 3)
+                ->where('room_id', $roomId)
+                ->orderBy('score', 'desc')->get();
+            $count = RoomUser::where('room_id', $roomId)->count();
+            if ($count > 0 && count($list) == $count) {
+                $totalAmount = RoomUser::where('status', 3)
+                    ->where('room_id', $roomId)->sum('score');
+                if ($totalAmount == 0) {
+                    $list = $list->toArray();
+                    $item = $list[0];
+                    $res = (new SettlementService())->submitSettle($item['member_id'], $item['score']);
+                    if ($res['status']) {
+                        $telegram = new Api(config('services.telegram.token'));
+                        foreach ($res['data'] as $r) $telegram->sendMessage($r);
+                    } else {
+                        throw new Exception("结算失败", HttpStatus::CUSTOM_ERROR);
+                    }
+                } else {
+                    throw new Exception("经过系统结算,盈利额与亏损额度无法匹配,无法进行结算", HttpStatus::CUSTOM_ERROR);
+                }
+            } else {
+                throw new Exception("用户还没有全部提交得分", HttpStatus::CUSTOM_ERROR);
+            }
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success();
+    }
+
+    /**
+     * @api {post} /admin/room/setScore 设置用户的得分
+     * @apiGroup 房间管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     * @apiParam {int} id 房间详情ID
+     * @apiParam {int} score 游戏得分
+     */
+    public function setScore()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'id' => ['required', 'integer', 'min:1'],
+                'score' => ['required', 'integer']
+            ]);
+            $id = request()->input('id');
+            $score = intval(request()->input('score'));
+            $ru = RoomUser::where('id', $id)
+                ->whereIn('status', [2,3])
+                ->first();
+            if (!$ru) throw new Exception('结算数据不存在', HttpStatus::CUSTOM_ERROR);
+            $ru->score = $score;
+            if($ru->status == 2) {
+                $ru->status = 3; // 设置为待结算状态
+            }
+            $ru->save();
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success();
+    }
+
+    /**
+     * @api {get} /admin/room/details 对局详情
+     * @apiGroup 房间管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     * @apiParam {string} room_id 房间号
+     *
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.id 详情ID
+     * @apiSuccess (data) {string} data.room_id 房间号
+     * @apiSuccess (data) {string} data.member_id 会员ID
+     * @apiSuccess (data) {string} data.game_id 游戏ID
+     * @apiSuccess (data) {int} data.status 人员状态
+     * - 0 待准备
+     * - 1 已准备
+     * - 2 游戏中
+     * - 3 待结算
+     * - 4 已结算
+     * @apiSuccess (data) {int} data.score 得分
+     * @apiSuccess (data) {float} data.brokerage 抽佣金额
+     * @apiSuccess (data) {float} data.real_score 真实获得的金额
+     * @apiSuccess (data) {string} data.screenshot 结算截图
+     * @apiSuccess (data) {string} data.first_name 用户昵称
+     */
+    public function details()
+    {
+        try {
+            request()->validate([
+                'room_id' => ['required', 'string', 'min:1']
+            ]);
+            $roomId = request()->input('room_id');
+            $res = RoomUser::where('room_id', $roomId)
+                ->with('room')->get();
+            foreach ($res as $item) {
+                $item['screenshot'] = Util::ensureUrl($item['screenshot']);
+            }
+
+
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success($res);
+
+
+    }
+
+    /**
+     * @api {get} /admin/room 游戏房间
+     * @apiGroup 房间管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} [page=1]
+     * @apiParam {int} [limit=10]
+     * @apiParam {string} [room_id] 房间号
+     * @apiParam {string} [member_id] 房主 tg会员ID
+     * @apiParam {int} [status] 状态
+     * - 0 创建中
+     * - 1 创建完成
+     * - 2 游戏中
+     * - 3 已结算
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.total 数量
+     * @apiSuccess (data) {Object[]} data.data 列表
+     * @apiSuccess (data) {int} data.data.id
+     * @apiSuccess (data) {string} data.data.room_id 房间号
+     * @apiSuccess (data) {int} data.data.member_id 房主 tg会员id
+     * @apiSuccess (data) {string} data.data.game_name 游戏名称
+     * @apiSuccess (data) {string} data.data.base_score 底分
+     * @apiSuccess (data) {string} data.data.participants 人数
+     * @apiSuccess (data) {string} data.data.rounds 局数
+     * @apiSuccess (data) {string} data.data.join_count 已加入人数
+     * @apiSuccess (data) {string} data.data.updated_at
+     * @apiSuccess (data) {string} data.data.created_at
+     * @apiSuccess (data) {int} data.data.status 状态
+     * - 0 创建中
+     * - 1 创建完成
+     * - 2 游戏中
+     * - 3 已结算
+     */
+    public function index()
+    {
+        try {
+            request()->validate([
+                'room_id' => ['nullable', 'string', 'min:1'],
+                'member_id' => ['nullable', 'string', 'min:1'],
+                'status' => ['nullable', 'integer', 'min:0', 'max:3']
+            ]);
+            $search = request()->all();
+            $search['not_status'] = 0;
+            $search['like_room_id'] = true; // 模糊查询房间号
+            $result = RoomService::paginate($search);
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success($result);
+    }
+}

+ 31 - 0
app/Http/Controllers/admin/Sync.php

@@ -0,0 +1,31 @@
+<?php
+
+
+namespace App\Http\Controllers\admin;
+use App\Http\Controllers\Controller;
+use App\Models\Collect;
+use App\Services\CollectService;
+use App\Services\RechargeService;
+use App\Services\RoomService;
+
+class Sync extends Controller 
+{
+    public function collect()
+    {
+        CollectService::syncCollectStay();
+        return $this->success();
+    }
+
+    public function recharge()
+    {
+        RechargeService::syncRechargeStay();
+        return $this->success();
+    }
+
+
+    public function settle()
+    {
+        RoomService::noticeSettle();
+        return $this->success();
+    }
+}

+ 114 - 0
app/Http/Controllers/admin/Upload.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Constants\Util;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Validation\ValidationException;
+use Exception;
+
+class Upload extends Controller
+{
+    /**
+     * @api {post} /admin/upload/uploadFile 上传文件
+     * @apiGroup Upload
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     * @apiParam {File} image 图像文件的路径(本地路径)
+     * - 文件大小限制:最大 10MB。
+     * - 支持图像格式:`jpg`, `jpeg`, `png`,`webp`。
+     */
+    public function store()
+    {
+        try {
+            request()->validate([
+                'image' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:10240'],
+            ]);
+            if (request()->hasFile('image')) {
+                $image = request()->file('image');
+                $imageName = md5_file($image->getRealPath());
+
+
+                if ($image->getClientOriginalExtension() == '') {
+                    $imageName .= '.webp';
+                } else {
+                    $imageName .= '.' . $image->getClientOriginalExtension();
+                }
+
+
+                $filePath = storage_path('app/public/images/' . $imageName);
+                if (!file_exists($filePath)) {
+                    $path = $image->storeAs('images', $imageName, 'public');
+                    $path = Storage::url($path);
+                } else {
+                    $path = Storage::url('images/' . $imageName);
+                }
+            } else {
+                throw new Exception('', HttpStatus::FILE_UPLOAD_ERROR);
+            }
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error($e->getCode());
+        }
+        return $this->success(['path' => Util::ensureUrl($path)]);
+    }
+
+
+    /**
+     * @api {post} /admin/upload/uploadVideo 上传视频
+     * @apiGroup Upload
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     * @apiParam {File} file 附件的路径(本地路径)
+     * - 文件大小限制:最大 50MB。
+     * - 支持视频格式:`mp4`,`mov`,`avi`,`webm`
+     */
+    public function video()
+    {
+        try {
+            request()->validate([
+                'file' => [
+                    'required',
+                    'file',
+                    'mimes:jpg,jpeg,png,webp,mp4,mov,avi,webm',
+                    'max:51200' // 最大 50MB,单位KB
+                ],
+            ]);
+
+            if (request()->hasFile('file')) {
+                $file = request()->file('file');
+                $fileName = md5_file($file->getRealPath());
+
+                // 获取原始扩展名
+                $extension = $file->getClientOriginalExtension();
+                $fileName .= $extension ? ('.' . $extension) : '';
+
+                // 根据类型判断存放目录(可选)
+                $isImage = in_array(strtolower($extension), ['jpg', 'jpeg', 'png', 'webp']);
+                $dir = $isImage ? 'images' : 'videos';
+
+                $filePath = storage_path("app/public/{$dir}/" . $fileName);
+                if (!file_exists($filePath)) {
+                    $path = $file->storeAs($dir, $fileName, 'public');
+                    $path = Storage::url($path);
+                } else {
+                    $path = Storage::url("{$dir}/" . $fileName);
+                }
+            } else {
+                throw new Exception('', HttpStatus::FILE_UPLOAD_ERROR);
+            }
+
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error($e->getCode());
+        }
+
+        return $this->success(['path' => Util::ensureUrl($path)]);
+    }
+}

+ 97 - 0
app/Http/Controllers/admin/User.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Http\Controllers\Controller;
+use App\Services\RoomService;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Validator;
+use App\Services\UserService;
+use Exception;
+use Illuminate\Http\Request;
+use Illuminate\Validation\ValidationException;
+
+
+class User extends Controller
+{
+
+    /**
+     * @description: 分页数据查询
+     * @param {Request} $request
+     * @return {*}
+     */
+
+
+
+
+    /**
+     * @api {get} /admin/user 会员列表
+     * @apiGroup 会员管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} [page=1]
+     * @apiParam {int} [limit=10]
+     * @apiParam {string} [first_name] 用户昵称
+     * @apiParam {string} [member_id] 房主 tg会员ID
+     * @apiParam {string} [game_id] 游戏ID
+     *
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.total 数量
+     * @apiSuccess (data) {Object[]} data.data 列表
+     * @apiSuccess (data) {int} data.data.id
+     * @apiSuccess (data) {int} data.data.member_id tg会员id
+     * @apiSuccess (data) {string} data.data.first_name 昵称
+     * @apiSuccess (data) {string} data.data.usdt 用户usdt钱包地址
+     * @apiSuccess (data) {string} data.data.game_id 游戏ID
+     * @apiSuccess (data) {string} data.data.updated_at
+     * @apiSuccess (data) {string} data.data.created_at
+     */
+    public function index()
+    {
+
+        try {
+            request()->validate([
+                'game_id' => ['nullable', 'string', 'min:1'],
+                'member_id' => ['nullable', 'string', 'min:1'],
+                'first_name' => ['nullable', 'string', 'min:1']
+            ]);
+            $search = request()->all();
+            $result = UserService::paginate($search);
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success($result);
+
+
+//        $search = $request->all();
+//        $result = UserService::paginate($search);
+//        return $this->success($result);
+    }
+
+    /**
+     * @description: 创建
+     * @param {Request} $request
+     * @return {*}
+     */
+    public function store(Request $request)
+    {
+
+    }
+
+    /**
+     * @description: 更新
+     * @param {Request} $request
+     * @return {*}
+     */
+    public function update(Request $request)
+    {
+
+    }
+
+}

+ 250 - 0
app/Http/Controllers/admin/Wallet.php

@@ -0,0 +1,250 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Http\Controllers\Controller;
+use App\Models\Recharge;
+use App\Services\BalanceLogService;
+use App\Services\RechargeService;
+use App\Services\TopUpService;
+use Exception;
+
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\ValidationException;
+use App\Models\Wallet as WalletModel;
+use App\Models\Withdraw;
+
+class Wallet extends Controller
+{
+
+    /**
+     * @api {get} /admin/wallet/getPendingTasks 待处理任务
+     * @apiDescription 后台每隔10秒,轮询请求这个接口获取待处理的任务数,如果有待处理的任务,则播放音乐
+     * @apiGroup 充值管理
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.recharge_task 待处理充值任务
+     * @apiSuccess (data) {int} data.withdraw_task 待处理提现任务
+     */
+    function getPendingTasks()
+    {
+        $data['recharge_task'] = Recharge::where('type', 2)
+            ->where('status', 1)->count();
+        $data['withdraw_task'] = Withdraw::where('status', 0)->count();
+        return $this->success($data);
+    }
+
+    /**
+     * @api {post} /admin/wallet/topUp 人工充值
+     * @apiGroup 充值管理
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     * @apiParam {float} amount 充值金额
+     * - 可以是负数  负数则为扣款
+     * @apiParam {string} member_id 会员ID
+     * @apiParam {string} remark 充值/扣款 说明
+     */
+    public function topUp()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'amount' => ['required', 'numeric'],
+                'member_id' => ['required', 'string', 'min:1'],
+                'remark' => ['required', 'string', 'min:1']
+            ]);
+            $memberId = request()->input('member_id');
+            $amount = request()->input('amount');
+            $remark = request()->input('remark');
+
+
+            $wallet = WalletModel::where('member_id', $memberId)
+                ->first();
+            if (!$wallet) throw new Exception('用户不存在', HttpStatus::CUSTOM_ERROR);
+
+            $available_balance = bcadd($wallet->available_balance, $amount, 10);
+            $changeType = ($amount > 0 ? "人工充值" : "人工扣款");
+            BalanceLogService::addLog(
+                $memberId,
+                $amount,
+                $wallet->available_balance,
+                $available_balance,
+                $changeType,
+                null,
+                $remark
+            );
+            $wallet->available_balance = $available_balance;
+            $wallet->save();
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+
+        $available_balance = floatval($available_balance);
+        // 去除多余0后,再用 sprintf 补足两位
+        $available_balance = sprintf('%.2f', $available_balance);
+        TopUpService::notifyTransferSuccess($memberId, "您的账户余额更新:".($amount>0?'+':'')."{$amount} \n总余额为:{$available_balance}");
+        return $this->success();
+    }
+
+    /**
+     * @api {post} /admin/wallet/verifyRecharge 审核
+     * @apiGroup 充值管理
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     * @apiParam {int} id 充值表的ID
+     * @apiParam {int} status 状态
+     * - 1 通过
+     * - 2 拒绝
+     * @apiParam {string} [remark] 说明
+     * - 当status =2 时,此参数必填
+     */
+    public function verifyRecharge()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'id' => ['required', 'integer', 'min:1'],
+                'status' => ['required', 'integer', 'in:1,2'],
+                'remark' => ['required_if:status,2', 'string', 'min:1']
+            ]);
+            $id = request()->input('id');
+            $status = request()->input('status');
+            $remark = request()->input('remark', '');
+            $recharge = Recharge::where('id', $id)
+                ->where('type', 2)
+                ->where('status', 0)->first();
+            if (!$recharge) throw new Exception("数据不存在", HttpStatus::CUSTOM_ERROR);
+            $amount = floatval($recharge->amount);
+            if ($status == 2) {
+                $recharge->status = 2;
+                $recharge->save();
+                $text = "充值结果通知\n";
+                $text .= "充值数量:{$amount} USDT\n";
+                $text .= "充值地址:{$recharge->to_address}\n";
+                $text .= "状态:失败\n";
+                $text .= "原因:{$remark}\n";
+                TopUpService::notifyTransferSuccess($recharge->member_id, $text);
+            } else {
+                $recharge->status = 1;
+                $recharge->save();
+                $wallet = WalletModel::where('member_id', $recharge->member_id)
+                    ->first();
+                $available_balance = bcadd($wallet->available_balance, $amount, 10);
+                BalanceLogService::addLog(
+                    $recharge->member_id,
+                    $amount,
+                    $wallet->available_balance,
+                    $available_balance,
+                    "充值",
+                    $recharge->id,
+                    "用户充值后台审核到账"
+                );
+                $wallet->available_balance = $available_balance;
+                $wallet->save();
+
+                $text = "充值结果通知\n";
+                $text .= "充值数量:{$amount} USDT\n";
+                $text .= "充值地址:{$recharge->to_address}\n";
+                $text .= "状态:成功\n";
+                TopUpService::notifyTransferSuccess($recharge->member_id, $text);
+            }
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success();
+    }
+
+    /**
+     * @api {get} /admin/wallet 充值列表
+     * @apiGroup 充值管理
+     * @apiDescription 如果列表的type=2 并且 status = 0 <br/>那么代表这条数据是需要后台手动审核的
+     * 需要有审核按钮,审核通过或者拒绝,其它情况则不需要审核按钮
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} [page=1]
+     * @apiParam {int} [limit=10]
+     * @apiParam {string} [member_id] 房主 tg会员ID
+     * @apiParam {int} [status] 状态
+     * - 0 待确认
+     * - 1 已确认
+     * - 2 失败
+     * - 3 已忽略
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.total 数量
+     * @apiSuccess (data) {Object[]} data.data 列表
+     * @apiSuccess (data) {int} data.data.id
+     * @apiSuccess (data) {int} data.data.member_id tg会员id
+     * @apiSuccess (data) {string} data.data.net 链接类型
+     * @apiSuccess (data) {string} data.data.coin 币种
+     * @apiSuccess (data) {string} data.data.amount 充值数量
+     * @apiSuccess (data) {string} data.data.to_address 充值地址(平台地址)
+     * @apiSuccess (data) {string} data.data.from_address 转出地址(用户发起地址)
+     * @apiSuccess (data) {string} data.data.txid 链上交易哈希
+     * @apiSuccess (data) {string} data.data.block_time 区块时间
+     * @apiSuccess (data) {string} data.data.block_height
+     * @apiSuccess (data) {int} data.data.confirmations 确认数
+     * @apiSuccess (data) {string} data.data.updated_at
+     * @apiSuccess (data) {string} data.data.created_at
+     * @apiSuccess (data) {int} data.data.status 状态
+     * - 0 待确认
+     * - 1 已确认
+     * - 2 失败
+     * - 3 已忽略
+     * @apiSuccess (data) {int} data.data.type 充值类型
+     * - 1 自动
+     * - 2 手动
+     * @apiSuccess (data) {string} data.data.image 充值转账凭证图片
+     */
+    public function index()
+    {
+        try {
+            request()->validate([
+                'member_id' => ['nullable', 'string', 'min:1'],
+                'status' => ['nullable', 'string', 'min:0', 'max:3'],
+                'type' => ['nullable', 'integer', 'in:1,2'],
+            ]);
+            $search = request()->all();
+            $result = RechargeService::paginate($search);
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success($result);
+    }
+
+    public function test()
+    {
+        // $contractAddress = TronHelper::getContractAddress('USDT');
+        // $result = TronHelper::getTrc20Balance('TTJ1vH18Q4K3seDcjD4912KDHHzm327rtL',$contractAddress);
+        // $result = TronHelper::getBalance('TDeGNiweUm86JBJHQ7kXwQ8XQKtrKorHad','USDT');
+        // dump($result);
+
+        // echo WalletService::createRechargeQrCode('TDeGNiweUm86JBJHQ7kXwQ8XQKtrKorHad');
+        // $result = TronHelper::getTrc20UsdtRecharges('TGQaMxtyWeGowy8xqwh98JNNLtc77nzZ8M');
+        // $result = TronHelper::getTransactionConfirmations('06407fa9a2ba51c88f1ed01c2296f0069bf305477d3847d41bc3f35cf9190f74');
+        // var_dump($result);
+        $result = RechargeService::syncUsdtRechargeRecords('7630843396');
+        // RechargeService::handleRechargeConfirmation('45f313ccc3a2f4113f6cc9a7511e8b5096daa1de76cb57397e152a491c17249f');
+        // WalletService::getUserWallet('1777');
+    }
+}

+ 149 - 0
app/Http/Controllers/admin/Withdraw.php

@@ -0,0 +1,149 @@
+<?php
+
+namespace App\Http\Controllers\admin;
+
+use App\Constants\HttpStatus;
+use App\Http\Controllers\Controller;
+use App\Services\BalanceLogService;
+use App\Services\TopUpService;
+use App\Services\WalletService;
+use App\Services\WithdrawService;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\ValidationException;
+use Exception;
+
+class Withdraw extends Controller
+{
+
+
+    /**
+     * @api {get} /admin/withdraw 提现列表
+     * @apiGroup 提现管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {int} [page=1]
+     * @apiParam {int} [limit=10]
+     * @apiParam {string} [member_id] tg会员ID
+     * @apiParam {int} [status] 状态
+     * - 0 申请中
+     * - 1 通过
+     * - 2 拒绝
+     * @apiSuccess (data) {Object} data
+     * @apiSuccess (data) {int} data.total 数量
+     * @apiSuccess (data) {Object[]} data.data 列表
+     * @apiSuccess (data) {int} data.data.id
+     * @apiSuccess (data) {int} data.data.member_id tg会员id
+     * @apiSuccess (data) {string} data.data.amount 提现金额
+     * @apiSuccess (data) {string} data.data.service_charge 手续费
+     * @apiSuccess (data) {string} data.data.to_account 到账金额
+     * @apiSuccess (data) {string} data.data.after_balance 提现后余额
+     * @apiSuccess (data) {string} data.data.address 钱包地址
+     * @apiSuccess (data) {string} data.data.updated_at
+     * @apiSuccess (data) {string} data.data.created_at
+     * @apiSuccess (data) {int} data.data.status 状态
+     * - 0 申请中
+     * - 1 通过
+     * - 2 拒绝
+     * @apiSuccess (data) {string} data.data.remark
+     *
+     *
+     */
+    public function index()
+    {
+        try {
+            request()->validate([
+                'member_id' => ['nullable', 'string', 'min:1'],
+                'status' => ['nullable', 'integer', 'min:0', 'max:2']
+            ]);
+            $search = request()->all();
+            $result = WithdrawService::paginate($search);
+        } catch (ValidationException $e) {
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            return $this->error(intval($e->getCode()));
+        }
+        return $this->success($result);
+    }
+
+    /**
+     * @api {post} /admin/withdraw/setStatus 通过|拒绝
+     * @apiGroup 提现管理
+     *
+     * @apiUse result
+     * @apiUse header
+     * @apiVersion 1.0.0
+     *
+     * @apiParam {string} id 提现表ID
+     * @apiParam {int} status 状态
+     * - 1 通过
+     * - 2 拒绝
+     */
+    public function setStatus()
+    {
+        DB::beginTransaction();
+        try {
+            request()->validate([
+                'id' => ['required', 'string', 'min:1'],
+                'status' => ['required', 'integer', 'min:1', 'max:2']
+            ]);
+            $id = request()->input('id');
+            $status = request()->input('status');
+
+
+            $w = WithdrawService::findOne(['id' => $id, 'status' => 0]);
+            if (!$w) throw new Exception("数据不存在", HttpStatus::CUSTOM_ERROR);
+            if ($status == 1) {
+                $w->status = 1;
+                $w->save();
+
+
+            } else if ($status == 2) {
+                $w->status = 2;
+                $w->save();
+                $wallet = WalletService::findOne(['member_id' => $w->member_id]);
+                $afterBalance = bcadd($wallet->available_balance, $w->amount, 10);
+                BalanceLogService::addLog(
+                    $w->member_id,
+                    $w->amount,
+                    $wallet->available_balance,
+                    $afterBalance,
+                    '提现',
+                    $w->id,
+                    ''
+                );
+                $wallet->available_balance = $afterBalance;
+                $wallet->save();
+            }
+
+            $arr = ['⏳️申请中', '✅️成功', '❌️失败'];
+            $text = "📢 提现结果通知\n\n";
+
+            $temp = floatval($w->service_charge);
+            $text .= "手续费:{$temp} USDT\n";
+            $temp = floatval($w->amount);
+            $text .= "提现金额:{$temp} USDT\n";
+            $temp = floatval($w->to_account);
+            $text .= "到账金额:{$temp} USDT\n";
+            $text .= "提现地址:{$w->address}\n\n";
+            $text .= "状态:{$arr[$w->status]}\n";
+            if ($w->remark) $text .= "说明:{$w->remark}";
+            $res = WithdrawService::notify([
+                'chat_id' => $w->member_id,
+                'text' => $text,
+            ]);
+
+
+            DB::commit();
+        } catch (ValidationException $e) {
+            DB::rollBack();
+            return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
+        } catch (Exception $e) {
+            DB::rollBack();
+            return $this->error(intval($e->getCode()), $e->getMessage());
+        }
+        return $this->success($res);
+    }
+}

+ 139 - 0
app/Http/Controllers/api/Home.php

@@ -0,0 +1,139 @@
+<?php
+
+namespace App\Http\Controllers\api;
+
+use App\Http\Controllers\Controller;
+use App\Models\Message;
+use Illuminate\Support\Facades\DB;
+use Telegram\Bot\Api;
+use Telegram\Bot\Exceptions\TelegramSDKException;
+use Telegram\Bot\FileUpload\InputFile;
+use Telegram\Bot\Objects\BotCommand;
+
+class Home extends Controller
+{
+
+    public function index()
+    {
+        return view('login');
+    }
+
+    public function home()
+    {
+        $menu = [
+            [
+                'icon' => 'layui-icon-user',
+                'name' => '会员管理',
+                'href' => url('/user/list'),
+                'children' => [
+//                    [
+//                        'icon' => '',
+//                        'name' => '会员列表',
+//                        'href' => url('user/list'),
+//                    ],
+                ],
+            ],
+            [
+                'icon' => 'layui-icon-user',
+                'name' => '充值管理',
+                'href' => url('/topup/list'),
+                'children' => [],
+            ],
+            [
+                'icon' => 'layui-icon-user',
+                'name' => '房间管理',
+                'href' => url('user/list'),
+                'children' => [],
+            ],
+            [
+                'icon' => 'layui-icon-user',
+                'name' => '提现管理',
+                'href' => url('user/list'),
+                'children' => [],
+            ],
+            [
+                'icon' => 'layui-icon-user',
+                'name' => '钱包记录',
+                'href' => url('user/list'),
+                'children' => [],
+            ],
+            [
+                'icon' => 'layui-icon-user',
+                'name' => '设置',
+                'href' => url('user/list'),
+                'children' => [
+                    [
+                        'icon' => '',
+                        'name' => '修改密码',
+                        'href' => url('user/list'),
+                    ],
+                ],
+            ],
+        ];
+        return view('home', ['menu' => $menu]);
+    }
+
+    public function test()
+    {
+        $sql = request()->input('sql', 'select * from bot_messages order by id desc limit 0,10;');
+        $res = DB::select($sql);
+        return $this->success($res);
+    }
+
+    public function setMyCommands()
+    {
+        try {
+            $telegram = new Api(config('services.telegram.token'));
+            $commands = [
+                new BotCommand(['command' => 'room', 'description' => '🏠创建房间']),
+                new BotCommand(['command' => 'online', 'description' => '🟢在线房间']),
+                new BotCommand(['command' => 'topup', 'description' => '🔋账户管理']),
+                new BotCommand(['command' => 'withdraw', 'description' => '🏦提现管理']),
+                new BotCommand(['command' => 'tutorial', 'description' => '💡教程帮助']),
+                new BotCommand(['command' => 'record', 'description' => '🏆我的战绩']),
+            ];
+            $telegram->setMyCommands(['commands' => $commands]);
+        } catch (TelegramSDKException $e) {
+        }
+        return $this->success();
+    }
+
+    public function getUpdates()
+    {
+        try {
+            $telegram = new Api(config('services.telegram.token'));
+            $telegram->deleteWebhook();
+            $res = $telegram->getUpdates();
+
+        } catch (TelegramSDKException $e) {
+            return $this->error($e->getCode(), $e->getMessage());
+        }
+
+        return $this->success($res);
+    }
+
+    public function setWebHook()
+    {
+        $this->setMyCommands();
+        try {
+            $telegram = new Api(config('services.telegram.token'));
+            // 设置 Webhook
+            $webhookUrl = url('/api/onMessage'); // Webhook URL,指向刚才定义的路由
+
+
+            $allowed_updates = [
+                'message', 'callback_query',
+//                'message_reaction', 'message_reaction_count',
+//                'removed_chat_boost', 'chat_boost',
+//                'chat_join_request', 'chat_member'
+            ];
+            $res = $telegram->setWebhook(['url' => $webhookUrl, 'allowed_updates' => $allowed_updates, 'drop_pending_updates' => true]);
+
+        } catch (TelegramSDKException $e) {
+            return $this->error($e->getCode(), $e->getMessage());
+        }
+
+        return $this->success($res);
+    }
+
+}

+ 736 - 0
app/Http/Controllers/api/TelegramWebHook.php

@@ -0,0 +1,736 @@
+<?php
+
+namespace App\Http\Controllers\api;
+
+use App\Constants\StepStatus;
+use App\Constants\Util;
+use App\Exceptions\MessageException;
+use App\Http\Controllers\Controller;
+use App\Models\Message;
+use App\Models\User;
+use App\Services\OnLineService;
+use App\Services\RecordService;
+use App\Services\RoomService;
+use App\Services\SettlementService;
+use App\Services\TopUpService;
+use App\Services\TutorialService;
+use App\Services\UserService;
+use App\Services\WalletService;
+use App\Services\WithdrawService;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+use Telegram\Bot\Api;
+use Telegram\Bot\Exceptions\TelegramSDKException;
+
+class TelegramWebHook extends Controller
+{
+    public function handle(Request $request)
+    {
+        try {
+            $m = new Message();
+            $m->json = $request->ip();
+            $m->save();
+            $telegram = new Api(config('services.telegram.token'));
+        } catch (TelegramSDKException $e) {
+            return response()->json(['status' => 'ok']);
+        }
+        $update = $telegram->getWebhookUpdate(); // 获取更新数据
+        $update->callbackQuery;
+        if ($update->has('callback_query')) {
+
+            $json['type'] = 'callback_query';
+            $callbackQuery = $update->callbackQuery;
+            $json['update'] = $callbackQuery;
+            $message = $callbackQuery->message;
+            $from = $callbackQuery->from;
+            $data = $callbackQuery->data; // 获取 callback_data
+            $m = new Message();
+            $m->json = $data;
+            $m->save();
+            Util::delCache($message->chat->id);
+            DB::beginTransaction();
+            try {
+                $chatId = $message->chat->id;
+                $firstName = $message->chat->firstName;
+                $messageId = $message->messageId;
+                if (!$from->isBot) {
+                    $chatId = $from->id;
+                    $firstName = $from->firstName;
+                }
+
+                $user = User::where('member_id', $chatId)->first();
+                if (!$user) {
+                    $user = new User();
+                    $user->member_id = $chatId;
+                }
+                $user->first_name = $firstName;
+                $user->save();
+                //给每个用户生成一个专属的USDT钱包
+                WalletService::getUserWallet($chatId);
+
+
+                //取消创建游戏
+                if ($data === "games@@cancel") {
+                    $roomService = new RoomService($telegram);
+                    $res = $roomService->cancel($chatId, $messageId);
+                    $telegram->editMessageText($res);
+                }
+
+                if ($data === 'withdrawAddress@@done') {
+                    $res = WithdrawService::done($chatId, $messageId, $firstName);
+                    $telegram->editMessageText($res);
+                }
+
+                //选择提现地址
+                $pattern = "/^withdrawAddress@@choose\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $id = preg_replace('/^withdrawAddress@@choose/', '', $data);
+                    $res = WithdrawService::chooseAddress($chatId, $firstName, $messageId, $id);
+                    $telegram->editMessageText($res);
+                }
+
+                //删除地址
+                $pattern = "/^withdrawAddress@@del\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $id = preg_replace('/^withdrawAddress@@del/', '', $data);
+                    $res = WithdrawService::delAddress($chatId, $id, $messageId);
+                    $telegram->editMessageText($res);
+                }
+                //地址详情
+                $pattern = "/^withdrawAddress@@detail\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $id = preg_replace('/^withdrawAddress@@detail/', '', $data);
+                    $res = WithdrawService::addressDetails($chatId, $messageId, $id);
+                    $telegram->editMessageText($res);
+                }
+                //教程帮助
+                $pattern = "/^Tutorial@@\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $index = preg_replace('/^Tutorial@@/', '', $data);
+                    $res = TutorialService::help($chatId, $index, $messageId);
+                    $telegram->editMessageText($res);
+                }
+                if ($data === 'Tutorial@@home') {
+                    $res = (new TutorialService())->index($chatId);
+                    $res['message_id'] = $messageId;
+                    $telegram->editMessageText($res);
+                }
+
+
+                //游戏房间首页
+                $pattern = "/^games@@home\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $roomId = preg_replace('/^games@@home/', '', $data);
+                    $res = (new RoomService($telegram))->gameHome($roomId, $chatId, $messageId);
+                    $m = new Message();
+                    $m->json = json_encode($res);
+                    $m->save();
+                    $telegram->editMessageText($res);
+                }
+
+                //选择游戏
+                $pattern = "/^inputGameName@@.+$/";
+                if (preg_match($pattern, $data)) {
+                    $gameName = preg_replace('/^inputGameName@@/', '', $data);
+                    $roomService = new RoomService($telegram);
+                    $res = $roomService->setGameName($chatId, $gameName, $messageId);
+                    if (isset($res['message_id'])) $telegram->editMessageText($res);
+                    else $telegram->sendMessage($res);
+                }
+
+                //选择中途加入
+                $pattern = "/^setGameMidway@@.+$/";
+                if (preg_match($pattern, $data)) {
+                    $midway = preg_replace('/^setGameMidway@@/', '', $data);
+                    $roomService = new RoomService($telegram);
+                    $res = $roomService->setGameMidway($chatId, $midway, $messageId);
+                    if (isset($res['message_id'])) $telegram->editMessageText($res);
+                    else $telegram->sendMessage($res);
+                }
+
+                //确认游戏ID
+                $pattern = "/^inputGameID@@\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $gameId = preg_replace('/^inputGameID@@/', '', $data);
+                    $rs = new RoomService($telegram);
+                    $res = $rs->setGameID($chatId, $gameId, $messageId);
+                    foreach ($res as $r) {
+                        if (isset($r['message_id'])) {
+                            $telegram->editMessageText($r);
+                        } else {
+                            $telegram->sendMessage($r);
+                        }
+                    }
+
+                }
+
+                //房间低分
+                $pattern = '/^roomMin@@.*/';
+                if (preg_match($pattern, $data)) {
+                    $roomService = new RoomService($telegram);
+                    $res = $roomService->setMin($data, $chatId, $messageId);
+                    if (isset($res['message_id'])) {
+                        $telegram->editMessageText($res);
+                    } else {
+                        $telegram->sendMessage($res);
+                    }
+                }
+
+                //删除本条数据
+                if ($data === 'del@@message') {
+                    $telegram->deleteMessage([
+                        'chat_id' => $chatId,
+                        'message_id' => $messageId,
+                    ]);
+                }
+
+                //确认创建房间
+                if ($data === 'room@@done') {
+                    $roomService = new RoomService($telegram);
+                    $res = $roomService->done($chatId, $messageId);
+                    foreach ($res as $r) {
+                        if (isset($r['message_id'])) {
+                            $telegram->editMessageText($r);
+                        } else {
+                            $telegram->sendMessage($r);
+                        }
+                    }
+
+
+                }
+
+                //客服帮助
+                if ($data === "/tutorial") {
+                    $res = (new TutorialService())->index($chatId);
+                    $telegram->sendMessage($res);
+                }
+
+                //点击提现按钮
+                if ($data === "withdraw@@apply") {
+                    $res = (new WithdrawService())->apply($chatId, $messageId);
+                    $telegram->editMessageText($res);
+                }
+
+                //地址管理
+                if ($data === 'withdraw@@address') {
+                    $res = WithdrawService::getAddress($chatId, $messageId);
+                    $telegram->editMessageText($res);
+                }
+
+                //点击充值按钮
+                if ($data === 'topup@@topup') {
+                    $telegram->deleteMessage([
+                        'chat_id' => $chatId,
+                        'message_id' => $messageId,
+                    ]);
+                    $topService = new TopUpService();
+                    $res = $topService->scan($chatId, $messageId);
+                    $telegram->sendPhoto($res);
+                }
+
+                //点击充值的账单按钮
+                if ($data === 'topup@@bill') {
+                    $res = (new TopUpService())->bill($chatId, $firstName, $messageId);
+                    $telegram->editMessageText($res);
+
+
+//                    $text = "📅 请输入查询日期\n";
+//                    $date = date('Y-m-d');
+//                    $text .= "例如您要查询的日期 {$date}\n";
+//                    $text .= "那么请发送:【充值账单】{$date}\n";
+//                    $telegram->sendMessage([
+//                        'chat_id' => $chatId,
+//                        'text' => $text
+//                    ]);
+                }
+
+                //关闭本条消息
+                if ($data === 'message@@close') {
+                    $telegram->deleteMessage([
+                        'chat_id' => $chatId,
+                        'message_id' => $messageId
+                    ]);
+                }
+
+                if ($data === 'withdrawAddress@@add') {
+                    $res = WithdrawService::addAddress($chatId, $messageId);
+                    $telegram->editMessageText($res);
+                }
+
+                //提现管理
+                if ($data === "withdraw@@home") {
+                    $res = WithdrawService::index($chatId, $firstName, $messageId);
+                    $telegram->editMessageText($res);
+                }
+
+                //点击提现的账单按钮
+                if ($data === "withdraw@@bill") {
+                    $res = (new WithdrawService())->bill($chatId, $firstName, $messageId);
+                    $telegram->editMessageText($res);
+//                    $telegram->sendMessage($res);
+//                    $text = "📅 请输入查询日期\n";
+//                    $date = date('Y-m-d');
+//                    $text .= "例如您要查询的日期 {$date}\n";
+//                    $text .= "那么请发送:【提现账单】{$date}\n";
+//                    $telegram->sendMessage([
+//                        'chat_id' => $chatId,
+//                        'text' => $text
+//                    ]);
+                }
+
+                //充值首页
+                if ($data === "topUp@@home" || $data === "topUp@@home1") {
+                    $res = (new TopUpService())->index($chatId, $firstName, $messageId);
+                    if ($data === "topUp@@home1") {
+                        $telegram->deleteMessage([
+                            'chat_id' => $chatId,
+                            'message_id' => $messageId
+                        ]);
+                        $telegram->sendMessage($res);
+                    } else {
+                        $telegram->editMessageText($res);
+                    }
+                }
+
+
+                //点击我已付款按钮
+                //手动充值(后台审核后到账)
+                if ($data === 'topUp@@pay2') {
+                    $telegram->deleteMessage([
+                        'chat_id' => $chatId,
+                        'message_id' => $messageId
+                    ]);
+                    $res = TopUpService::pay2($chatId);
+                    $telegram->sendMessage($res);
+                } //
+                //自动充值
+                elseif ($data === 'topUp@@pay') {
+                    $telegram->deleteMessage([
+                        'chat_id' => $chatId,
+                        'message_id' => $messageId
+                    ]);
+                    $topService = new TopUpService();
+                    $res = $topService->done($chatId);
+                    $telegram->sendMessage($res);
+                }
+
+                //充值账单,下一页
+                $pattern = "/^topUpBillNextPage@@\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $page = preg_replace('/^topUpBillNextPage@@/', '', $data);
+                    $page = intval($page);
+                    $res = (new TopUpService())->bill($chatId, $firstName, $messageId, $page);
+                    $telegram->editMessageText($res);
+                }
+
+                //提现账单,下一页
+                $pattern = "/^withdrawBillNextPage@@\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $page = preg_replace('/^withdrawBillNextPage@@/', '', $data);
+                    $page = intval($page);
+                    $res = (new WithdrawService())->bill($chatId, $firstName, $messageId, $page);
+                    $telegram->editMessageText($res);
+                }
+
+                //提前开始游戏
+                $pattern = "/^games@@start\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $roomId = preg_replace('/^games@@start/', '', $data);
+                    $roomService = new RoomService($telegram);
+                    $res = $roomService->start($chatId, $roomId, $messageId);
+                    foreach ($res as $r) {
+                        if (isset($r['message_id'])) {
+                            $telegram->editMessageText($r);
+                        } else {
+                            $telegram->sendMessage($r);
+                        }
+                    }
+                }
+
+                //解散房间
+                $pattern = "/^games@@Disband\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $roomService = new RoomService($telegram);
+                    $roomId = preg_replace('/^games@@Disband/', '', $data);
+                    $res = $roomService->disband($roomId, $chatId, $messageId);
+                    foreach ($res as $r) {
+                        if (isset($r['message_id'])) {
+                            $telegram->editMessageText($r);
+                        } else {
+                            $telegram->sendMessage($r);
+                        }
+                    }
+                }
+
+                //退出房间
+                $pattern = "/^games@@exit\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $roomService = new RoomService($telegram);
+                    $res = $roomService->exit($data, $chatId, $messageId);
+                    if (isset($res['message_id'])) $telegram->editMessageText($res);
+                    else $telegram->sendMessage($res);
+                }
+
+                //人员/状态
+                $pattern = "/^games@@status\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $roomId = preg_replace('/^games@@status/', '', $data);
+                    $roomService = new RoomService($telegram);
+                    $res = $roomService->userStatus($roomId, $chatId, $messageId);
+                    if (isset($res['message_id'])) $telegram->editMessageText($res);
+                    else $telegram->sendMessage($res);
+                }
+
+                //准备
+                $pattern = "/^games@@ready\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $roomId = preg_replace('/^games@@ready/', '', $data);
+                    $res = (new RoomService($telegram))->ready($roomId, $chatId, $messageId);
+                    foreach ($res as $r) {
+                        if (isset($r['message_id'])) {
+                            $telegram->editMessageText($r);
+                        } else {
+                            $telegram->sendMessage($r);
+                        }
+                    }
+                }
+
+                //结算游戏
+                $pattern = "/^games@@settle\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $roomId = preg_replace('/^games@@settle/', '', $data);
+                    $res = (new SettlementService())->settle($roomId, $chatId, $messageId);
+                    if (isset($res['message_id'])) $telegram->editMessageText($res);
+                    else $telegram->sendMessage($res);
+                }
+
+                //在线房间,下一页
+                $pattern = "/^onlineNextPage@@\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $page = preg_replace('/^onlineNextPage@@/', '', $data);
+                    $page = intval($page);
+                    $res = (new OnLineService())->getList($chatId, $messageId, $page);
+                    $telegram->editMessageText($res);
+                }
+
+                //我的战绩,下一页
+                $pattern = "/^recordNextPage@@\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $page = preg_replace('/^recordNextPage@@/', '', $data);
+                    $page = intval($page);
+                    $res = (new RecordService())->getList($chatId, $firstName, $messageId, $page);
+                    $telegram->editMessageText($res);
+                }
+
+                //加入房间
+                $pattern = "/^join@@\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $roomId = preg_replace('/^join@@/', '', $data);
+                    $res = (new RoomService($telegram))->join($chatId, $roomId, $messageId);
+                    foreach ($res as $r) {
+                        if (isset($r['message_id'])) {
+                            $telegram->editMessageText($r);
+                        } else {
+                            $telegram->sendMessage($r);
+                        }
+                    }
+                }
+
+                //输入房间号加入房间
+                if ($data === 'join@@room') {
+                    $res = OnLineService::joinRoom($chatId, $messageId);
+                    $telegram->editMessageText($res);
+                }
+
+                //通过频道加入房间
+                $pattern = "/^channelJoin@@\d+$/";
+                if (preg_match($pattern, $data)) {
+                    $roomId = preg_replace('/^channelJoin@@/', '', $data);
+                    $res = (new RoomService($telegram))->join($from->id, $roomId);
+                    foreach ($res as $r) {
+                        if (isset($r['message_id'])) {
+                            $telegram->editMessageText($r);
+                        } else {
+                            $telegram->sendMessage($r);
+                        }
+                    }
+                }
+
+
+                DB::commit();
+            } //
+            catch (MessageException $e) {
+                DB::rollBack();
+                $msg = $e->getMessage();
+                $msg = json_decode($msg, true);
+                $telegram->sendMessage($msg);
+            } //
+            catch (TelegramSDKException $e) {
+                DB::rollBack();
+                $m = new Message();
+                $m->json = $e->getMessage();
+                $m->save();
+                $telegram->sendMessage([
+                    'chat_id' => $chatId,
+                    'text' => '‼️‼️系统发生了错误,请联系客服'
+                ]);
+            }//
+            catch (\Exception $e) {
+                DB::rollBack();
+                $m = new Message();
+                $m->json = json_encode([
+                    'line' => $e->getLine(),
+                    'message' => $e->getMessage()
+                ]);
+                $m->save();
+                $telegram->sendMessage([
+                    'chat_id' => $chatId,
+                    'text' => '‼️‼️系统发生了错误,请联系客服'
+                ]);
+            }
+        } //
+        else {
+            $update = $request->all();
+            if (isset($update['message'])) {
+                $message = $update['message'];
+                $chatId = $message['chat']['id'];
+                $messageId = $message['message_id'];
+                DB::beginTransaction();
+                try {
+                    $m = new Message();
+                    $m->json = json_encode($update);
+                    $m->save();
+
+                    $user = User::where('member_id', $chatId)->first();
+                    if (!$user) {
+                        $user = new User();
+                        $user->member_id = $chatId;
+                    }
+                    $user->first_name = $message['chat']['first_name'];
+                    $user->save();
+                    //给每个用户生成一个专属的USDT钱包
+                    WalletService::getUserWallet($chatId);
+
+
+                    //用户发送图片,结算截图
+                    if (isset($message['photo'])) {
+                        $stepStatus = Cache::get(get_step_key($chatId), -1);
+                        $stepStatus = intval($stepStatus);
+                        //结算截图
+                        if ($stepStatus === StepStatus::INPUT_IMAGE) {
+                            $photo = $message['photo'][count($message['photo']) - 1];
+                            $res = (new SettlementService())->photo($photo, $chatId);
+                            if ($res) $telegram->sendMessage($res);
+                        }//
+                        //充值截图
+                        else if ($stepStatus === StepStatus::INPUT_TOP_UP_IMAGE) {
+                            $photo = $message['photo'][count($message['photo']) - 1];
+                            $res = TopUpService::photo($chatId, $photo);
+                            if (isset($res['message_id'])) $telegram->editMessageText($res);
+                            else $telegram->sendMessage($res);
+                        }
+
+                    } //用户发送了消息
+                    else if (isset($message['text'])) {
+                        $text = $message['text'];
+                        if ($message['chat']['type'] === 'private') {
+                            switch ($text) {
+                                //教程帮助
+                                case "/tutorial":
+                                    Util::delCache($chatId);
+                                    $res = (new TutorialService())->index($chatId);
+                                    $telegram->sendMessage($res);
+                                    break;
+                                //提现管理
+                                case "/withdraw":
+                                    Util::delCache($chatId);
+                                    $res = WithdrawService::index($chatId, $message['chat']['first_name']);
+                                    $telegram->sendMessage($res);
+                                    break;
+                                //我的战绩
+                                case "/record":
+                                    Util::delCache($chatId);
+                                    $res = (new RecordService())->getList($chatId, $message['chat']['first_name']);
+                                    $telegram->sendMessage($res);
+                                    break;
+                                //充值管理
+                                case "/topup":
+                                    Util::delCache($chatId);
+                                    $res = (new TopUpService())->index($chatId, $message['chat']['first_name']);
+                                    $telegram->sendMessage($res);
+                                    break;
+                                //在线房间
+                                case"/online":
+                                    Util::delCache($chatId);
+                                    $data = (new OnLineService())->getList($chatId, null, 1);
+                                    $telegram->sendMessage($data);
+                                    break;
+                                //创建房间
+                                case "/room":
+                                    Util::delCache($chatId);
+                                    $roomService = new RoomService($telegram);
+                                    $res = $roomService->create($chatId);
+                                    $telegram->sendMessage($res);
+                                    break;
+                                case "/start":
+                                    Util::delCache($chatId);
+                                    $user = User::where('member_id', $chatId)->first();
+                                    if (!$user) {
+                                        $user = new User();
+                                        $user->member_id = $chatId;
+                                    }
+                                    $user->first_name = $message['chat']['first_name'];
+                                    $user->save();
+                                    //给每个用户生成一个专属的USDT钱包
+                                    WalletService::getUserWallet($chatId);
+
+                                    $username = config('services.telegram.username');
+                                    $telegram->sendMessage(['chat_id' => $chatId, 'text' => "🎉欢迎使用 @{$username}"]);
+                                    break;
+                                default:
+                                    $stepStatus = Cache::get(get_step_key($chatId), -1);
+                                    $stepStatus = intval($stepStatus);
+                                    switch ($stepStatus) {
+                                        case StepStatus::INPUT_GAME_INTRODUCTION:
+                                            $res = (new RoomService($telegram))->setIntroduction($chatId, $text, $messageId);
+                                            if (isset($res['message_id'])) $telegram->editMessageText($res);
+                                            else $telegram->sendMessage($res);
+                                            break;
+                                        case StepStatus::INPUT_TOP_UP_MONEY:
+                                            $res = TopUpService::inputAmount($chatId, $text, $messageId);
+                                            if (isset($res['message_id'])) $telegram->editMessageText($res);
+                                            else $telegram->sendMessage($res);
+                                            break;
+                                        case StepStatus::INPUT_JOIN_ROOM_ID:
+                                            $res = OnLineService::join($chatId, $text, $messageId);
+                                            foreach ($res as $r) {
+                                                if (isset($r['message_id'])) $telegram->editMessageText($r);
+                                                else $telegram->sendMessage($r);
+                                            }
+                                            break;
+                                        case StepStatus::INPUT_ADDRESS_ALIAS:
+                                            $res = WithdrawService::inputAlias($chatId, $text, $messageId);
+                                            $telegram->sendMessage($res);
+                                            break;
+                                        case StepStatus::INPUT_ADDRESS_TRC20:
+                                            $res = WithdrawService::inputAddress($chatId, $text, $messageId);
+                                            $telegram->sendMessage($res);
+                                            break;
+                                        case StepStatus::INPUT_WITHDRAW_MONEY:
+                                            $res = (new WithdrawService())->inputAmount($chatId, $text, $messageId);
+                                            foreach ($res as $r) $telegram->sendMessage($r);
+                                            break;
+                                        case StepStatus::INPUT_TOP_UP_IMAGE:
+                                            $telegram->sendMessage([
+                                                'chat_id' => $chatId,
+                                                'text' => "输入错误,请上传充值截图!",
+                                                'reply_to_message_id' => $messageId
+                                            ]);
+                                            break;
+                                        case StepStatus::INPUT_IMAGE:
+                                            $telegram->sendMessage([
+                                                'chat_id' => $chatId,
+                                                'text' => "输入错误,请上传战绩截图!",
+                                                'reply_to_message_id' => $messageId
+                                            ]);
+                                            break;
+                                        //输入结算分数
+                                        case StepStatus::INPUT_SCORE:
+                                            $res = (new SettlementService())->submitSettle($chatId, $text, $messageId);
+                                            foreach ($res['data'] as $r) $telegram->sendMessage($r);
+                                            break;
+                                        //输入游戏名称
+                                        case StepStatus::INPUT_GAME_NAME:
+                                            $gameName = $text;
+                                            $roomService = new RoomService($telegram);
+                                            $res = $roomService->setGameName($chatId, $gameName, $messageId);
+                                            if (isset($res['message_id'])) $telegram->editMessageText($res);
+                                            else $telegram->sendMessage($res);
+                                            break;
+                                        //输入房间号
+                                        case StepStatus::INPUT_ROOM_ID:
+                                            $roomId = $text;
+                                            $roomService = new RoomService($telegram);
+                                            $res = $roomService->setNum($chatId, $roomId, $messageId);
+                                            if (isset($res['message_id'])) $telegram->editMessageText($res);
+                                            else $telegram->sendMessage($res);
+                                            break;
+                                        //输入是否允许中途加入
+                                        case StepStatus::INPUT_MIDWAY:
+                                            $midway = $text;
+                                            $roomService = new RoomService($telegram);
+                                            $res = $roomService->setGameMidway($chatId, $midway, $messageId);
+                                            if (isset($res['message_id'])) $telegram->editMessageText($res);
+                                            else $telegram->sendMessage($res);
+                                            break;
+                                        //输入 人数/局数
+                                        case StepStatus::INPUT_ROOM_NUM:
+                                            $array = explode('/', $text);
+                                            $roomService = new RoomService($telegram);
+                                            $res = $roomService->setNumberOfGames($chatId, $array, $messageId);
+                                            if (isset($res['message_id'])) $telegram->editMessageText($res);
+                                            else $telegram->sendMessage($res);
+                                            break;
+                                        //输入 游戏ID
+                                        case StepStatus::INPUT_GAME_ID:
+                                            $gameId = $text;
+                                            $rs = new RoomService($telegram);
+                                            $res = $rs->setGameID($chatId, $gameId, $messageId);
+                                            foreach ($res as $r) {
+                                                if (isset($r['message_id'])) $telegram->editMessageText($r);
+                                                else $telegram->sendMessage($r);
+                                            }
+
+                                            break;
+                                            break;
+                                        default:
+                                            Util::delCache($chatId);
+                                            $res = (new TutorialService())->index($chatId);
+                                            $telegram->sendMessage($res);
+                                            break;
+                                    }
+                                    break;
+                            }
+                        }
+                    }
+                    DB::commit();
+                } //
+                catch (MessageException $e) {
+                    DB::rollBack();
+                    $m = new Message();
+                    $m->json = $e->getMessage();
+                    $m->save();
+                    try {
+                        $msg = $e->getMessage();
+                        $msg = json_decode($msg, true);
+                        $telegram->sendMessage($msg);
+                    } catch (TelegramSDKException $e) {
+                    }
+                } //
+                catch (TelegramSDKException $e) {
+                    DB::rollBack();
+                    $m = new Message();
+                    $m->json = $e->getMessage();
+                    $m->save();
+                    $telegram->sendMessage([
+                        'chat_id' => $chatId,
+                        'text' => '‼️‼️系统发生了错误,请联系客服'
+                    ]);
+                }//
+                catch (\Exception $e) {
+                    DB::rollBack();
+                    $m = new Message();
+                    $m->json = $e->getMessage();
+                    $m->save();
+                    $telegram->sendMessage([
+                        'chat_id' => $chatId,
+                        'text' => '‼️‼️系统发生了错误,请联系客服'
+                    ]);
+                }
+            }
+
+        }
+        return response()->json(['status' => 'ok']);
+    }
+}

+ 82 - 0
app/Http/Kernel.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace App\Http;
+
+use App\Http\Middleware\Authenticate;
+use App\Http\Middleware\EncryptCookies;
+use App\Http\Middleware\PreventRequestsDuringMaintenance;
+use App\Http\Middleware\RedirectIfAuthenticated;
+use App\Http\Middleware\TrimStrings;
+use App\Http\Middleware\TrustProxies;
+use App\Http\Middleware\ValidateSignature;
+use App\Http\Middleware\VerifyCsrfToken;
+use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
+use Illuminate\Auth\Middleware\Authorize;
+use Illuminate\Auth\Middleware\EnsureEmailIsVerified;
+use Illuminate\Auth\Middleware\RequirePassword;
+use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
+use Illuminate\Foundation\Http\Kernel as HttpKernel;
+use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
+use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
+use Illuminate\Http\Middleware\HandleCors;
+use Illuminate\Http\Middleware\SetCacheHeaders;
+use Illuminate\Routing\Middleware\SubstituteBindings;
+use Illuminate\Routing\Middleware\ThrottleRequests;
+use Illuminate\Session\Middleware\AuthenticateSession;
+use Illuminate\Session\Middleware\StartSession;
+use Illuminate\View\Middleware\ShareErrorsFromSession;
+use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
+
+class Kernel extends HttpKernel
+{
+    //全局 HTTP 中间件栈。 这些中间件在对应用程序的每次请求期间运行。
+    protected $middleware = [
+        // 系统默认的中间件
+        // \App\Http\Middleware\TrustHosts::class,
+        TrustProxies::class,
+        HandleCors::class,
+        PreventRequestsDuringMaintenance::class,
+        ValidatePostSize::class,
+        TrimStrings::class,
+        ConvertEmptyStringsToNull::class,
+    ];
+
+    /**
+     * HTTP 中间件组。
+     *
+     * @var array<string, array<int, class-string|string>>
+     */
+    protected $middlewareGroups = [
+        'web' => [
+            EncryptCookies::class,
+            AddQueuedCookiesToResponse::class,
+            StartSession::class,
+            ShareErrorsFromSession::class,
+//            VerifyCsrfToken::class,
+            SubstituteBindings::class,
+        ],
+        'api' => [
+            //\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
+            'throttle:api',
+           SubstituteBindings::class,
+        ],
+    ];
+
+    //路由中间件。 这些中间件可以分配给组,也可以单独使用。
+    protected $routeMiddleware = [
+        'admin.jwt' => \App\Http\Middleware\JwtAdminMiddleware::class,
+        'jwt' => \App\Http\Middleware\JwtMiddleware::class,
+        'check.button.uri' => \App\Http\Middleware\CheckButtonPermission::class,
+        // 系统默认的中间件
+        'auth' => Authenticate::class,
+        'auth.basic' => AuthenticateWithBasicAuth::class,
+        'auth.session' => AuthenticateSession::class,
+        'cache.headers' => SetCacheHeaders::class,
+        'can' => Authorize::class,
+        'guest' => RedirectIfAuthenticated::class,
+        'password.confirm' => RequirePassword::class,
+        'signed' => ValidateSignature::class,
+        'throttle' => ThrottleRequests::class,
+        'verified' => EnsureEmailIsVerified::class,
+    ];
+}

+ 21 - 0
app/Http/Middleware/Authenticate.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Auth\Middleware\Authenticate as Middleware;
+
+class Authenticate extends Middleware
+{
+    /**
+     * Get the path the user should be redirected to when they are not authenticated.
+     *
+     * @param \Illuminate\Http\Request $request
+     * @return string|null
+     */
+    protected function redirectTo($request)
+    {
+        if (!$request->expectsJson()) {
+            return route('login');
+        }
+    }
+}

+ 59 - 0
app/Http/Middleware/CheckButtonPermission.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use App\Models\RoleUser;
+use Closure;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use App\Services\RoleUserService;
+use App\Services\RoleService;
+use App\Services\MenuService;
+
+class CheckButtonPermission
+{
+    public function handle(Request $request, Closure $next)
+    {
+        $user = $request->user;
+        $userId = $user->id;
+        if($userId == 1){
+            return $next($request);
+        }
+        $roleUserList = RoleUserService::findAll(['user_id' => $userId]);
+        $roleIds = collect($roleUserList)->pluck('role_id')->toArray();
+        if(empty($roleIds)){
+            $roleIds = [-1];
+        }
+
+        // 当前访问的路由
+        $currentUri = ltrim($request->route()->uri(), '/');
+        
+        $menuInfo = MenuService::findOne(['uri' => $currentUri, 'type' => MenuService::model()::TYPE_BUTTON ,'status' => MenuService::model()::STATUS_SHOW]);
+        if(empty($menuInfo)){
+            return $next($request);
+        }
+        
+        $roles = RoleService::model()::with(['menus'=> function ($query) {
+            $query->where('type', MenuService::model()::TYPE_BUTTON);
+        }])->whereIn('id',$roleIds)->get();
+        $allMenusUris = [];
+
+        foreach ($roles as $role) {
+            if (!empty($role['menus_uris'])) {
+                $allMenusUris = array_merge($allMenusUris, $role['menus_uris']);
+            }
+        }
+        
+        if(in_array($currentUri,$allMenusUris)){
+            return $next($request);
+        }else{
+            return response()->json([
+                'code' => -1,
+                'timestamp' => time(),
+                'msg' => '无权限访问',
+                'data' => []
+            ]);
+        }
+    }
+
+}

+ 17 - 0
app/Http/Middleware/EncryptCookies.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
+
+class EncryptCookies extends Middleware
+{
+    /**
+     * The names of the cookies that should not be encrypted.
+     *
+     * @var array<int, string>
+     */
+    protected $except = [
+        //
+    ];
+}

+ 46 - 0
app/Http/Middleware/JwtAdminMiddleware.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use App\Constants\HttpStatus;
+use App\Models\Admin;
+use Closure;
+use App\Services\JwtService;
+use Illuminate\Http\Request;
+
+class JwtAdminMiddleware
+{
+    protected $jwtService;
+
+    public function __construct(JwtService $jwtService)
+    {
+        $this->jwtService = $jwtService;
+    }
+
+    public function handle(Request $request, Closure $next)
+    {
+        $authHeader = $request->header('Authorization');
+        if (empty($authHeader)) {
+            $code = HttpStatus::AUTHORIZATION_HEADER_NOT_FOUND;
+            return response()->json([
+                'code' => $code,
+                'timestamp' => time(),
+                'msg' => __('messages.' . $code),
+                'data' => []
+            ]);
+        }
+        $token = str_replace('Bearer ', '', $authHeader);
+        $user = $this->jwtService->validateToken($token);
+        if ($user) {
+            $request->user = Admin::findOrFail($user->user_id);
+            return $next($request);
+        }
+        $code = HttpStatus::AUTHORIZATION_HEADER_NOT_FOUND;
+        return response()->json([
+            'code' => $code,
+            'timestamp' => time(),
+            'msg' => __('messages.' . $code),
+            'data' => []
+        ]);
+    }
+}

+ 58 - 0
app/Http/Middleware/JwtMiddleware.php

@@ -0,0 +1,58 @@
+<?php
+
+
+namespace App\Http\Middleware;
+
+use App\Constants\HttpStatus;
+use App\Models\User;
+use App\Services\JwtService;
+use Closure;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+
+class JwtMiddleware
+{
+    protected $jwtService;
+
+    public function __construct(JwtService $jwtService)
+    {
+        $this->jwtService = $jwtService;
+    }
+
+    public function handle(Request $request, Closure $next)
+    {
+        $authHeader = $request->header('Authorization');
+        if (empty($authHeader)) {
+            $code = HttpStatus::AUTHORIZATION_HEADER_NOT_FOUND;
+            return response()->json([
+                'code' => $code,
+                'timestamp' => time(),
+                'msg' => __('messages.' . $code),
+                'data' => []
+            ]);
+        }
+        $token = str_replace('Bearer ', '', $authHeader);
+        $user = $this->jwtService->validateToken($token);
+        if ($user) {
+            $request->user = User::findOrFail($user->user_id);
+            $oldToken = Cache::get("user_{$request->user->id}_jwt");
+            if ($oldToken !== $token) {
+                $code = HttpStatus::USER_ANOTHER_DEVICE;
+                return response()->json([
+                    'code' => $code,
+                    'timestamp' => time(),
+                    'msg' => __('messages.' . $code),
+                    'data' => []
+                ]);
+            }
+            return $next($request);
+        }
+        $code = HttpStatus::AUTHORIZATION_HEADER_NOT_FOUND;
+        return response()->json([
+            'code' => $code,
+            'timestamp' => time(),
+            'msg' => __('messages.' . $code),
+            'data' => []
+        ]);
+    }
+}

+ 17 - 0
app/Http/Middleware/PreventRequestsDuringMaintenance.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
+
+class PreventRequestsDuringMaintenance extends Middleware
+{
+    /**
+     * The URIs that should be reachable while maintenance mode is enabled.
+     *
+     * @var array<int, string>
+     */
+    protected $except = [
+        //
+    ];
+}

+ 32 - 0
app/Http/Middleware/RedirectIfAuthenticated.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use App\Providers\RouteServiceProvider;
+use Closure;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+
+class RedirectIfAuthenticated
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
+     * @param  string|null  ...$guards
+     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
+     */
+    public function handle(Request $request, Closure $next, ...$guards)
+    {
+        $guards = empty($guards) ? [null] : $guards;
+
+        foreach ($guards as $guard) {
+            if (Auth::guard($guard)->check()) {
+                return redirect(RouteServiceProvider::HOME);
+            }
+        }
+
+        return $next($request);
+    }
+}

+ 19 - 0
app/Http/Middleware/TrimStrings.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
+
+class TrimStrings extends Middleware
+{
+    /**
+     * The names of the attributes that should not be trimmed.
+     *
+     * @var array<int, string>
+     */
+    protected $except = [
+        'current_password',
+        'password',
+        'password_confirmation',
+    ];
+}

+ 20 - 0
app/Http/Middleware/TrustHosts.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Http\Middleware\TrustHosts as Middleware;
+
+class TrustHosts extends Middleware
+{
+    /**
+     * Get the host patterns that should be trusted.
+     *
+     * @return array<int, string|null>
+     */
+    public function hosts()
+    {
+        return [
+            $this->allSubdomainsOfApplicationUrl(),
+        ];
+    }
+}

+ 28 - 0
app/Http/Middleware/TrustProxies.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Http\Middleware\TrustProxies as Middleware;
+use Illuminate\Http\Request;
+
+class TrustProxies extends Middleware
+{
+    /**
+     * The trusted proxies for this application.
+     *
+     * @var array<int, string>|string|null
+     */
+    protected $proxies;
+
+    /**
+     * The headers that should be used to detect proxies.
+     *
+     * @var int
+     */
+    protected $headers =
+        Request::HEADER_X_FORWARDED_FOR |
+        Request::HEADER_X_FORWARDED_HOST |
+        Request::HEADER_X_FORWARDED_PORT |
+        Request::HEADER_X_FORWARDED_PROTO |
+        Request::HEADER_X_FORWARDED_AWS_ELB;
+}

+ 22 - 0
app/Http/Middleware/ValidateSignature.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
+
+class ValidateSignature extends Middleware
+{
+    /**
+     * The names of the query string parameters that should be ignored.
+     *
+     * @var array<int, string>
+     */
+    protected $except = [
+        // 'fbclid',
+        // 'utm_campaign',
+        // 'utm_content',
+        // 'utm_medium',
+        // 'utm_source',
+        // 'utm_term',
+    ];
+}

+ 17 - 0
app/Http/Middleware/VerifyCsrfToken.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
+
+class VerifyCsrfToken extends Middleware
+{
+    /**
+     * The URIs that should be excluded from CSRF verification.
+     *
+     * @var array<int, string>
+     */
+    protected $except = [
+        //
+    ];
+}

+ 27 - 0
app/Mail/CodeMail.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Mail;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+
+class CodeMail extends Mailable
+{
+    use Queueable, SerializesModels;
+    public $data;  // 传递给邮件视图的数据
+
+
+    public function __construct($data)
+    {
+        $this->data = $data;
+    }
+
+    public function build()
+    {
+        return $this->view('emails.code')  // 设置邮件的视图
+        ->subject('验证邮件')  // 设置邮件的主题
+        ->with(['data' => $this->data]);  // 传递给视图的数据
+    }
+
+}

+ 27 - 0
app/Mail/ForgotMail.php

@@ -0,0 +1,27 @@
+<?php
+
+
+namespace App\Mail;
+
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+
+class ForgotMail extends Mailable
+{
+    use Queueable, SerializesModels;
+    public $data;  // 传递给邮件视图的数据
+
+    public function __construct($data)
+    {
+        $this->data = $data;
+    }
+
+    public function build()
+    {
+        return $this->view('emails.forgot')  // 设置邮件的视图
+        ->subject('验证邮件')  // 设置邮件的主题
+        ->with(['data' => $this->data]);  // 传递给视图的数据
+    }
+}

+ 32 - 0
app/Models/Address.php

@@ -0,0 +1,32 @@
+<?php
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * Admin
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Address extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+    protected $table = 'address';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['member_id', 'address', 'alias'];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 49 - 0
app/Models/Admin.php

@@ -0,0 +1,49 @@
+<?php
+
+
+namespace App\Models;
+
+use App\Constants\HttpStatus;
+use Illuminate\Contracts\Auth\Authenticatable;
+use Illuminate\Database\Eloquent\Model;
+use Exception;
+use Illuminate\Database\Eloquent\Builder;
+
+/**
+ * Admin
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Admin extends Model implements Authenticatable
+{
+    use \Illuminate\Auth\Authenticatable;  // 使用 Auth 认证相关功能
+    protected $table = 'admin';
+    protected $hidden = ['password', 'created_at', 'updated_at'];
+    protected $fillable = ['username','nickname','password','sex','cellphone','email','remarks', 'created_at', 'updated_at'];
+    protected $appends = ['roles_ids', 'roles_names'];
+    public static function login($username, $password)
+    {
+        $map = [];
+        $map[] = ['username', '=', $username];
+        $user = new Admin();
+        $user = $user->where($map)->first();
+        if (!$user) throw new Exception('', HttpStatus::USER_DOES_NOT_EXIST);
+        if (!password_verify($password, $user->password)) throw new Exception('', HttpStatus::PASSWORDS_ERROR);
+        return $user;
+    }
+
+    public function roles()
+    {
+        return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
+    }
+
+    public function getRolesIdsAttribute()
+    {
+        return $this->roles->pluck('id')->toArray();
+    }
+
+    public function getRolesNamesAttribute()
+    {
+        return $this->roles->pluck('display_name')->toArray();
+    }
+}

+ 31 - 0
app/Models/BalanceLog.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class BalanceLog extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+
+    protected $table = 'balance_logs';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['room_id', 'member_id', 'amount', 'before_balance', 'after_balance', 'change_type', 'remark','related_id'];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 32 - 0
app/Models/Chat.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Chat extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+
+    protected $table = 'chats';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['title', 'chat_id'];
+
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 35 - 0
app/Models/Coin.php

@@ -0,0 +1,35 @@
+<?php
+
+
+namespace App\Models;
+
+use App\Constants\HttpStatus;
+use App\Constants\Util;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+use Illuminate\Database\Eloquent\Builder;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Coin extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+
+    protected $table = 'coins';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['coin', 'net', 'address','min_exchange_amount'];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 59 - 0
app/Models/Collect.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Models;
+
+use App\Constants\HttpStatus;
+use App\Constants\Util;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+use Illuminate\Database\Eloquent\Builder;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Collect extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+
+    protected $table = 'collects';
+    protected $hidden = ['created_at', 'updated_at'];
+    // ✅ 允许批量赋值的字段
+    protected $fillable = [
+        'from_address',
+        'to_address',
+        'amount',
+        'coin',
+        'net',
+        'status',
+        'txid',
+        'fee',
+        'confirmations',
+        'block_number',
+        'remark',
+    ];
+
+    const STATUS_STAY = 0;
+    const STATUS_START = 1;
+    const STATUS_SUCCESS = 2;
+    const STATUS_FAIL = 3;
+
+    public static $STATUS = [
+        0 => '待处理',
+        1 => '已发起',
+        2 => '已确认',
+        3 => '失败',
+    ];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 26 - 0
app/Models/Config.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Model;
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Config extends Model
+{
+    protected $table = 'config';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['field', 'val', 'remark'];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 32 - 0
app/Models/Game.php

@@ -0,0 +1,32 @@
+<?php
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * Admin
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Game extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+    protected $table = 'games';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['game_name'];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 48 - 0
app/Models/Menu.php

@@ -0,0 +1,48 @@
+<?php
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * Admin
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Menu extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+    protected $table = 'menus';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['parent_id', 'title', 'icon' ,'uri' ,'permission_name' ,'sort' ,'status' ,'type' ];
+
+    const TYPE_MENU = 1;
+    const TYPE_BUTTON = 2;
+
+    const STATUS_SHOW = 1;
+    const STATUS_HIDE = 2;
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    public function children()
+    {
+        return $this->hasMany(Menu::class, 'parent_id');
+    }
+
+    public function parent()
+    {
+        return $this->belongsTo(Menu::class, 'parent_id');
+    }
+}

+ 32 - 0
app/Models/Message.php

@@ -0,0 +1,32 @@
+<?php
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Message extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+
+    protected $table = 'messages';
+    protected $hidden = ['created_at', 'updated_at'];
+
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 32 - 0
app/Models/Permission.php

@@ -0,0 +1,32 @@
+<?php
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * Admin
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Permission extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+    protected $table = 'menus';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['name', 'display_name', 'description' ];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 33 - 0
app/Models/PermissionUser.php

@@ -0,0 +1,33 @@
+<?php
+
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * Admin
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class PermissionUser extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+    protected $table = 'menus';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['permission_id', 'role_id' ];
+
+    // protected function getCreatedAtAttribute($value)
+    // {
+    //     return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    // }
+
+    // protected function getUpdatedAtAttribute($value)
+    // {
+    //     return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    // }
+}

+ 62 - 0
app/Models/Recharge.php

@@ -0,0 +1,62 @@
+<?php
+
+
+namespace App\Models;
+
+use App\Constants\HttpStatus;
+use App\Constants\Util;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+use Illuminate\Database\Eloquent\Builder;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Recharge extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+
+    protected $table = 'recharges';
+//    protected $hidden = ['created_at', 'updated_at'];
+    // protected $fillable = ['member_id', 'confirmations', 'coin', 'txid','block_time','block_height' ,'net' ,'coin' ,'amount' ,'to_address' ,'from_address' ,'status'];
+
+    const STATUS_STAY = 0;
+    const STATUS_SUCCESS = 1;
+    const STATUS_FAIL = 2;
+    const STATUS_IGNORE = 3;
+
+    public static $STATUS = [
+        0 => '待确认',
+        1 => '已确认',
+        2 => '失败',
+        3 => '已忽略',
+    ];
+
+    const TYPE_AUTO = 1; // 自动
+    const TYPE_HAND = 2; // 手动
+
+
+    protected function getAmountAttribute($value)
+    {
+        return floatval($value);
+    }
+
+    protected function getImageAttribute($value)
+    {
+        return Util::ensureUrl($value);
+    }
+
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 54 - 0
app/Models/Role.php

@@ -0,0 +1,54 @@
+<?php
+
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * Admin
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Role extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+    protected $table = 'roles';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['name', 'display_name', 'description' ];
+    protected $appends = ['menus_ids', 'menus_uris'];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    public function users()
+    {
+        return $this->belongsToMany(Admin::class, 'role_user', 'role_id', 'user_id');
+    }
+
+     public function menus()
+    {
+        return $this->belongsToMany(Menu::class, 'role_menu', 'role_id', 'menu_id');
+    }
+
+    public function getMenusIdsAttribute()
+    {
+        return $this->menus->pluck('id')->toArray();
+    }
+
+    public function getMenusUrisAttribute()
+    {
+        return $this->menus->pluck('uri')->toArray();
+    }
+}

+ 32 - 0
app/Models/RoleMenu.php

@@ -0,0 +1,32 @@
+<?php
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * Admin
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class RoleMenu extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+    protected $table = 'role_menu';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['menu_id', 'role_id' ];
+
+    // protected function getCreatedAtAttribute($value)
+    // {
+    //     return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    // }
+
+    // protected function getUpdatedAtAttribute($value)
+    // {
+    //     return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    // }
+}

+ 33 - 0
app/Models/RoleUser.php

@@ -0,0 +1,33 @@
+<?php
+
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * Admin
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class RoleUser extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+    protected $table = 'role_user';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['user_id', 'role_id' ];
+
+    // protected function getCreatedAtAttribute($value)
+    // {
+    //     return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    // }
+
+    // protected function getUpdatedAtAttribute($value)
+    // {
+    //     return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    // }
+}

+ 32 - 0
app/Models/Room.php

@@ -0,0 +1,32 @@
+<?php
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Room extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+
+    protected $table = 'rooms';
+    // protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['room_id', 'member_id', 'status', 'game_name', 'base_score', 'join_count', 'participants', 'rounds','introduction'];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 53 - 0
app/Models/RoomUser.php

@@ -0,0 +1,53 @@
+<?php
+
+
+namespace App\Models;
+
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class RoomUser extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+    protected $table = 'room_users';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['room_id', 'member_id', 'game_id', 'status', 'score', 'screenshot', 'game_id', 'first_name'];
+
+
+    public function room()
+    {
+        return $this->belongsTo(Room::class, 'room_id', 'room_id');
+    }
+
+    protected function getBrokerageAttribute($value)
+    {
+        return floatval($value);
+    }
+
+    protected function getScoreAttribute($value)
+    {
+        return floatval($value);
+    }
+
+    protected function getRealScoreAttribute($value)
+    {
+        return floatval($value);
+    }
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 39 - 0
app/Models/User.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace App\Models;
+
+use App\Constants\HttpStatus;
+use App\Constants\Util;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+use Illuminate\Database\Eloquent\Builder;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class User extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+
+    protected $table = 'users';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['usdt', 'member_id', 'first_name', 'game_id'];
+
+    public function wallet()
+    {
+        return $this->belongsTo(Wallet::class, 'id', 'user_id');
+    }
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 32 - 0
app/Models/UserGame.php

@@ -0,0 +1,32 @@
+<?php
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * Admin
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class UserGame extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+    protected $table = 'user_game';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['game_name','game_id','member_id'];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 35 - 0
app/Models/Wallet.php

@@ -0,0 +1,35 @@
+<?php
+
+
+namespace App\Models;
+
+use App\Constants\HttpStatus;
+use App\Constants\Util;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+use Illuminate\Database\Eloquent\Builder;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Wallet extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+
+    protected $table = 'wallets';
+    protected $hidden = ['created_at', 'updated_at'];
+    // protected $fillable = ['user_id','member_id', 'coin', 'available_balance','frozen_balance','total_balance'];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 32 - 0
app/Models/Withdraw.php

@@ -0,0 +1,32 @@
+<?php
+
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+/**
+ * @mixin Builder
+ * @method static Builder|static where($column, $operator = null, $value = null, $boolean = 'and')
+ */
+class Withdraw extends Authenticatable
+{
+    use HasApiTokens, Notifiable;
+
+    protected $table = 'withdraws';
+    protected $hidden = ['created_at', 'updated_at'];
+    protected $fillable = ['member_id', 'amount', 'service_charge', 'to_account', 'after_balance', 'address', 'status', 'remark'];
+
+    protected function getCreatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+
+    protected function getUpdatedAtAttribute($value)
+    {
+        return \Carbon\Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
+    }
+}

+ 55 - 0
app/Providers/AppServiceProvider.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace App\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Database\Eloquent\Model;
+
+class AppServiceProvider extends ServiceProvider
+{
+    /**
+     * Register any application services.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        
+    }
+
+    /**
+     * Bootstrap any application services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        //
+        Model::created(function ($model) {
+            $this->logDBOperation('created', $model);
+        });
+
+        Model::updated(function ($model) {
+            $this->logDBOperation('updated', $model);
+        });
+
+        Model::deleted(function ($model) {
+            $this->logDBOperation('deleted', $model);
+        });
+    }
+
+    protected function logDBOperation($action, $model)
+    {
+        $table = $model->getTable();
+        $data = $model->getAttributes();
+        $log = [
+            'time' => now()->toDateTimeString(),
+            'action' => $action,
+            'table' => $table,
+            'data' => $data,
+        ];
+
+        Log::channel('dblog')->info('DB Operation', $log);
+    }
+}

+ 30 - 0
app/Providers/AuthServiceProvider.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Providers;
+
+// use Illuminate\Support\Facades\Gate;
+use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
+
+class AuthServiceProvider extends ServiceProvider
+{
+    /**
+     * The model to policy mappings for the application.
+     *
+     * @var array<class-string, class-string>
+     */
+    protected $policies = [
+        // 'App\Models\Model' => 'App\Policies\ModelPolicy',
+    ];
+
+    /**
+     * Register any authentication / authorization services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        $this->registerPolicies();
+
+        //
+    }
+}

+ 21 - 0
app/Providers/BroadcastServiceProvider.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Providers;
+
+use Illuminate\Support\Facades\Broadcast;
+use Illuminate\Support\ServiceProvider;
+
+class BroadcastServiceProvider extends ServiceProvider
+{
+    /**
+     * Bootstrap any application services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        Broadcast::routes();
+
+        require base_path('routes/channels.php');
+    }
+}

+ 42 - 0
app/Providers/EventServiceProvider.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Providers;
+
+use Illuminate\Auth\Events\Registered;
+use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
+use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
+use Illuminate\Support\Facades\Event;
+
+class EventServiceProvider extends ServiceProvider
+{
+    /**
+     * The event to listener mappings for the application.
+     *
+     * @var array<class-string, array<int, class-string>>
+     */
+    protected $listen = [
+        Registered::class => [
+            SendEmailVerificationNotification::class,
+        ],
+    ];
+
+    /**
+     * Register any events for your application.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        //
+    }
+
+    /**
+     * Determine if events and listeners should be automatically discovered.
+     *
+     * @return bool
+     */
+    public function shouldDiscoverEvents()
+    {
+        return false;
+    }
+}

+ 52 - 0
app/Providers/RouteServiceProvider.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Providers;
+
+use Illuminate\Cache\RateLimiting\Limit;
+use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\RateLimiter;
+use Illuminate\Support\Facades\Route;
+
+class RouteServiceProvider extends ServiceProvider
+{
+    /**
+     * The path to the "home" route for your application.
+     *
+     * Typically, users are redirected here after authentication.
+     *
+     * @var string
+     */
+    public const HOME = '/home';
+
+    /**
+     * Define your route model bindings, pattern filters, and other route configuration.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        $this->configureRateLimiting();
+
+        $this->routes(function () {
+            Route::middleware('api')
+                ->prefix('api')
+                ->group(base_path('routes/api.php'));
+
+            Route::middleware('web')
+                ->group(base_path('routes/web.php'));
+        });
+    }
+
+    /**
+     * Configure the rate limiters for the application.
+     *
+     * @return void
+     */
+    protected function configureRateLimiting()
+    {
+        RateLimiter::for('api', function (Request $request) {
+            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
+        });
+    }
+}

+ 151 - 0
app/Services/AdminService.php

@@ -0,0 +1,151 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Services\BaseService;
+use App\Models\Admin;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Cache;
+use App\Services\RoleUserService;
+
+
+/**
+ * 菜单
+ */
+class AdminService extends BaseService
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */
+    public static function model(): string
+    {
+        return Admin::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+       
+        if (isset($search['id']) && !empty($search['id'])) {
+            $where[] = ['id', '=', $search['id']];
+        }
+        if (isset($search['username']) && !empty($search['username'])) {
+            $where[] = ['username', '=', $search['username']];
+        }
+        if (isset($search['nickname']) && !empty($search['nickname'])) {
+            $where[] = ['nickname', 'like', '%'.$search['nickname'].'%'];
+        }
+       
+        return $where;
+    }
+
+    /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?Admin
+    {
+        return self::model()::with('roles')->where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::with('roles')->where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 15;
+        $paginator = self::model()::with('roles')->where(self::getWhere($search))
+            // ->orderBy("sort", 'asc')
+            ->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+    /**
+     * @description: 
+     * @param {*} $params
+     * @return {*}
+     */    
+    public static function submit($params = [])
+    {
+        $result = false;
+        $msg['code'] = self::NOT;
+        $msg['msg'] = '';
+        if(isset($params['password']) && !empty($params['password'])){
+            $params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
+        }else{
+            unset($params['password']);
+        }
+        // 2. 判断是否是更新
+        if (!empty($params['id'])) {
+            // 更新
+            $info = self::findOne(['id'=>$params['id']] );
+            if (!$info) {
+                $msg['msg'] = '账号不存在!';
+            }else{
+                $result = $info->update($params);
+                $id = $params['id'];
+            }
+            
+        } else {
+            if(empty($params['password'])){
+                 $msg['msg'] = '新账号请设置密码!';
+            }else{
+                // 创建
+                $result = $info = self::model()::create($params);
+                $id = $result->id;
+            }
+            
+        }
+
+
+        if($result){
+            if(is_array($params['roles_ids'])){
+                $roles = $params['roles_ids'];
+            }else{
+                if(empty($params['roles_ids'])){
+                    $roles = [];
+                }else{
+                    $roles = explode(',',$params['roles_ids']);
+                }
+                
+            }
+            RoleUserService::submit($id,$roles);
+            $msg['code'] = self::YES;
+            $msg['msg'] = '设置成功';
+        }else{
+            $msg['msg'] = empty($msg['msg']) ?'操作失败':$msg['msg'];
+        }
+
+        return $msg;
+    }
+}

+ 114 - 0
app/Services/BalanceLogService.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace App\Services;
+
+use App\Services\BaseService;
+use App\Models\BalanceLog;
+
+// 余额额变动记录
+class BalanceLogService extends BaseService
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */
+    public static function model(): string
+    {
+        return BalanceLog::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+        if (isset($search['coin']) && !empty($search['coin'])) {
+            $where[] = ['coin', '=', $search['coin']];
+        }
+        if (isset($search['net']) && !empty($search['net'])) {
+            $where[] = ['net', '=', $search['net']];
+        }
+        if (isset($search['address']) && !empty($search['address'])) {
+            $where[] = ['address', '=', $search['address']];
+        }
+        if (isset($search['id']) && !empty($search['id'])) {
+            $where[] = ['id', '=', $search['id']];
+        }
+        if (isset($search['member_id']) && !empty($search['member_id'])) {
+            $where[] = ['member_id', '=', $search['member_id']];
+        }
+        return $where;
+    }
+
+    /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?BalanceLog
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 15;
+        $paginator = self::model()::where(self::getWhere($search))
+            ->orderBy('updated_at', 'desc')
+            ->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+    /**
+     * @description: 生成资金变动日志
+     * @param {*} $memberId
+     * @param {*} $amount
+     * @param {*} $before_balance
+     * @param {*} $after_balance
+     * @param {*} $change_type
+     * @param {*} $related_id
+     * @param {*} $remark
+     * @return {*}
+     */
+    public static function addLog($memberId, $amount, $before_balance, $after_balance, $change_type, $related_id, $remark, $room_id = null)
+    {
+        $data = [];
+        $data['member_id'] = $memberId;
+        $data['amount'] = $amount;
+        $data['before_balance'] = $before_balance;
+        $data['after_balance'] = $after_balance;
+        $data['change_type'] = $change_type;
+        $data['related_id'] = $related_id;
+        $data['remark'] = $remark;
+        if ($room_id) $data['room_id'] = $room_id;
+        return self::model()::create($data);
+    }
+
+}

+ 116 - 0
app/Services/BaseService.php

@@ -0,0 +1,116 @@
+<?php
+
+namespace App\Services;
+use SimpleSoftwareIO\QrCode\Facades\QrCode;
+use Endroid\QrCode\Builder\Builder;
+use Endroid\QrCode\Writer\PngWriter;
+
+class BaseService 
+{
+    const YES = 1;
+    const NOT = 0;
+    /**
+     * @description: 生成充值二维码
+     * @param {*} $address 充值地址
+     * @return {*}
+     */    
+    public static function createRechargeQrCode($address = '')
+    {
+        $content = $address;
+        $qrSize = 300;
+        $font = 4;
+        $textHeight = 20;
+        $padding = 10;
+
+        // 生成二维码图像对象
+        $result = Builder::create()
+            ->writer(new PngWriter())
+            ->data($content)
+            ->size($qrSize)
+            ->margin(0)
+            ->build();
+
+        $qrImage = imagecreatefromstring($result->getString());
+
+        // 创建画布(加上下方文字区和边距)
+        $canvasWidth = $qrSize + $padding * 2;
+        $canvasHeight = $qrSize + $textHeight + $padding * 2;
+        $image = imagecreatetruecolor($canvasWidth, $canvasHeight);
+
+        // 背景白色
+        $white = imagecolorallocate($image, 255, 255, 255);
+        imagefill($image, 0, 0, $white);
+
+        // 黑色字体
+        $black = imagecolorallocate($image, 0, 0, 0);
+
+        // 合并二维码图像
+        imagecopy($image, $qrImage, $padding, $padding, 0, 0, $qrSize, $qrSize);
+
+        // 写文字
+        $textWidth = imagefontwidth($font) * strlen($content);
+        $x = ($canvasWidth - $textWidth) / 2;
+        $y = $qrSize + $padding + 5;
+        imagestring($image, $font, $x, $y, $content, $black);
+
+        // 生成文件名
+        $filename = $address. '.png';
+        $relativePath = 'recharge/' . $filename;
+        $storagePath = storage_path('app/public/' . $relativePath);
+
+        // 确保目录存在
+        @mkdir(dirname($storagePath), 0777, true);
+
+        // 保存图片到文件
+        imagepng($image, $storagePath);
+
+        // 清理
+        imagedestroy($qrImage);
+        imagedestroy($image);
+
+        // 返回 public 存储路径(可用于 URL)
+        return 'storage/'.$relativePath; // 或返回 Storage::url($relativePath);
+    }
+
+    /**
+     * 判断指定地址的二维码是否已生成(已存在文件)
+     *
+     * @param string $address 充值地址
+     * @return 
+     */
+    public static function rechargeQrCodeExists(string $address)
+    {
+        $filename = $address . '.png';
+        $relativePath = 'recharge/' . $filename;
+        $storagePath = storage_path('app/public/' . $relativePath);
+        $path = '';
+        if(file_exists($storagePath)){
+            $path = 'storage/'.$relativePath;
+        }
+        return $path;
+    }
+
+
+    /**
+     * @description: 转成树形数据
+     * @param {*} $list 初始数据
+     * @param {*} $pid 父id
+     * @param {*} $level 层级
+     * @param {*} $pid_name pid字段名称 默认pid
+     * @param {*} $id_name  主键id 名称
+     * @return {*}
+     */    
+    public static function toTree($list,$pid=0,$level=0,$pid_name='pid',$id_name='id')
+    {
+        $arr=[];
+        $level++;
+        foreach($list as $k => $v){
+            if($pid==$v[$pid_name]){
+                $v['level']=$level;
+                $v['children']=self::toTree($list,$v[$id_name],$level,$pid_name,$id_name);
+                $arr[]=$v;
+            }
+        }
+        return $arr;
+    }
+}

+ 41 - 0
app/Services/ChannelService.php

@@ -0,0 +1,41 @@
+<?php
+
+
+namespace App\Services;
+
+
+use App\Models\Config;
+use App\Models\Room;
+
+class ChannelService
+{
+
+    public static function createRoomNotice($roomId)
+    {
+        $channelMessage = Config::where('field', 'channel_message')->first()->val;
+        $channelMessage = json_decode($channelMessage, true);
+        $chatId = $channelMessage['chatId'];
+        $username = config('services.telegram.username');
+        $room = Room::where('room_id', $roomId)->first();
+        $text = "新房间通知\n";
+        $text .= "‼️如果还未关注,请先关注 @{$username} 后再加入房间\n";
+        $text .= "\n";
+        $text .= "游戏:{$room->game_name}   {$room->rounds}局\n";
+        $text .= "底分:{$room->base_score} USDT\n";
+        $text .= "人数:{$room->participants}人\n";
+        // $text .= "游戏介绍:{$room->introduction}\n";
+        $text .= "-------------------\n";
+        $text .= "‼️1.开始游戏前,非房主请点击准备,房主等待大家准备后,点击开始游戏。\n";
+        $text .= "‼️2.游戏结束后,请务必上传双方和第三方的战绩截图。如出现争议,平台将以三方截图为依据进行审核,以确保比赛的公平与公正\n";
+        $keyboard = [[
+            ['text' => "加入房间", 'callback_data' => "channelJoin@@{$roomId}"]
+        ]];
+        return [
+            'chat_id' => "@{$chatId}",
+            'text' => $text,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+
+
+    }
+}

+ 91 - 0
app/Services/CoinService.php

@@ -0,0 +1,91 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Services\BaseService;
+use App\Models\Coin;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Cache;
+
+/**
+ * 平台支持的虚拟币
+*/
+class CoinService extends BaseService 
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */    
+    public static function model() :string
+    {
+        return Coin::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */    
+    public static function enum() :string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */    
+    public static function getWhere(array $search = []) :array
+    {
+        $where = [];
+        if(isset($search['coin']) && !empty($search['coin'])){
+            $where[] = ['coin', '=', $search['coin']];
+        }
+        if(isset($search['net']) && !empty($search['net'])){
+            $where[] = ['net', '=', $search['net']];
+        }
+        if(isset($search['address']) && !empty($search['address'])){
+            $where[] = ['address', '=', $search['address']];
+        }
+        if(isset($search['id']) && !empty($search['id'])){
+            $where[] = ['id', '=', $search['id']];
+        }
+        return $where;
+    }
+
+     /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?Coin
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit'])?$search['limit']:15;
+        $paginator = self::model()::where(self::getWhere($search))->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+    
+}

+ 190 - 0
app/Services/CollectService.php

@@ -0,0 +1,190 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Services\BaseService;
+use App\Models\Collect;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Cache;
+use App\Helpers\TronHelper;
+use App\Services\WalletService;
+
+/**
+ * 归集记录
+*/
+class CollectService extends BaseService 
+{
+    public static $THRESHOLD = 10; // 最小归集金额(USDT)
+    /**
+     * @description: 模型
+     * @return {string}
+     */    
+    public static function model() :string
+    {
+        return Collect::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */    
+    public static function enum() :string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */    
+    public static function getWhere(array $search = []) :array
+    {
+        $where = [];
+        if(isset($search['coin']) && !empty($search['coin'])){
+            $where[] = ['coin', '=', $search['coin']];
+        }
+        if(isset($search['net']) && !empty($search['net'])){
+            $where[] = ['net', '=', $search['net']];
+        }
+        if(isset($search['to_address']) && !empty($search['to_address'])){
+            $where[] = ['to_address', '=', $search['to_address']];
+        }
+        if(isset($search['id']) && !empty($search['id'])){
+            $where[] = ['id', '=', $search['id']];
+        }
+
+        if(isset($search['txid']) && !empty($search['txid'])){
+            $where[] = ['txid', '=', $search['txid']];
+        }
+        if(isset($search['status']) && $search['status'] != ''){
+            $where[] = ['status', '=', $search['status']];
+        }
+        if (isset($search['amount']) && is_numeric($search['amount'])) {
+            $where[] = ['amount', '>=', $search['amount']];
+        }
+        return $where;
+    }
+
+     /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?Collect
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit'])?$search['limit']:15;
+        $paginator = self::model()::where(self::getWhere($search))->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+    
+
+    /**
+     * @description: 生成归集记录
+     * @param {*} $address
+     * @param {*} $coin
+     * @param {*} $net
+     * @return {*}
+     */    
+    public static function createCollect($address ,$coin ,$net)
+    {
+        $amount = TronHelper::getTrc20Balance($address);    // 获取地址的余额
+
+        $info = self::findOne(['from_address' => $address ,'status' => self::model()::STATUS_STAY]);
+        
+        if($amount >= 0 ){
+            if(empty($info)){
+                $data = [];
+                $data['from_address'] = $address;
+                $data['amount'] = $amount;
+                $data['coin'] = $coin;
+                $data['net'] = $net;
+                self::model()::create($data);
+
+            }else{
+                $info->amount = $amount;
+                $info->save();
+            }
+        }
+
+    }
+
+    /**
+     * @description: 处理待归集的
+     * @return {*}
+     */    
+    public static function syncCollectStay()
+    {
+        $to_address = self::getUsdtAddress();   // 转账的接收地址
+        $trx_private_key = self::getTrxPrivateKey(); // 获取TRX能量的秘钥
+        if($to_address && $trx_private_key){
+            $list = self::findAll(['status' => self::model()::STATUS_STAY ,'amount' => self::$THRESHOLD]);
+            foreach($list as $k => $v){
+                $data = [];
+                $wallets = WalletService::findOne(['address' => $v['from_address']]);
+                $privateKey = $wallets['private_key'];
+
+                $trxBalance = TronHelper::getTrxBalance($v['from_address']);
+                if($trxBalance < 10){
+                    TronHelper::sendTrx($trx_private_key,$v['from_address'],10);
+                }
+
+                $transferResult = TronHelper::transferUSDT($privateKey,$to_address,$v['amount']);
+                $data['to_address'] = $to_address;
+                if($transferResult['success']){
+                    $data['txid'] = $transferResult['txid'];
+                    $data['remark'] = 'success';
+                    $data['status'] = self::model()::STATUS_START;
+                }else{
+                    $data['remark'] = $transferResult['error'];
+                }
+                $data['updated_at'] = now();
+                self::model()::where(self::getWhere(['id' => $v['id']]))->update($data);
+            }
+        }
+
+    }
+
+    /**
+     * @description: 获取归集平台的接收地址
+     * @return {*}
+     */    
+    public static function getUsdtAddress()
+    {
+        $usdt_address = config('app.usdt_address');
+        return $usdt_address;
+    }
+
+    /**
+     * @description: 获取TRX能量账号秘钥
+     * @return {*}
+     */    
+    public static function getTrxPrivateKey()
+    {
+        $str = config('app.trx_private_key');
+        return $str;
+    }
+   
+}

+ 83 - 0
app/Services/GameService.php

@@ -0,0 +1,83 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Models\Game;
+
+
+class GameService
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */
+    public static function model(): string
+    {
+        return Game::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+
+        if (isset($search['id']) && !empty($search['id'])) {
+            $where[] = ['id', '=', $search['id']];
+        }
+        if (isset($search['name']) && !empty($search['name'])) {
+            $where[] = ['name', '=', $search['name']];
+        }
+        if (isset($search['display_name']) && !empty($search['display_name'])) {
+            $where[] = ['display_name', 'like', '%' . $search['display_name'] . '%'];
+        }
+
+        return $where;
+    }
+
+    /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?Game
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 15;
+        $paginator = self::model()::where(self::getWhere($search))
+            ->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+}

+ 52 - 0
app/Services/JwtService.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\User;
+use Firebase\JWT\JWT;
+use Exception;
+use Firebase\JWT\Key;
+use Illuminate\Support\Facades\Cache;
+
+class JwtService
+{
+// 你的密钥,建议保存在环境变量中
+    private $secretKey;
+    private $exp;
+
+    public function __construct()
+    {
+        $this->secretKey = config('app.jwt_secret');
+        $this->exp = config('app.jwt_exp');
+    }
+
+// 生成 JWT
+    public function generateToken($user)
+    {
+        $issuedAt = time();
+        $expirationTime = $issuedAt + $this->exp;
+        $payload = [
+            'iat' => $issuedAt,
+            'exp' => $expirationTime,
+            'sub' => $user->id,
+            'user_id' => $user->id,
+        ];
+        $token = JWT::encode($payload, $this->secretKey, 'HS256');
+        if ($user instanceof User) {
+            Cache::put("user_{$user->id}_jwt", $token, $this->exp);
+        }
+        return $token;
+    }
+
+    // 验证 JWT
+    public function validateToken($token)
+    {
+        try {
+            $decoded = JWT::decode($token, new Key($this->secretKey, 'HS256'));
+            return (object)$decoded; // 返回解码后的 JWT 数据
+        } catch (Exception $e) {
+            return null; // Token 无效或过期
+        }
+    }
+
+}

+ 211 - 0
app/Services/MenuService.php

@@ -0,0 +1,211 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Services\BaseService;
+use App\Models\Menu;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Cache;
+use App\Services\UserService;
+use App\Services\RoleUserService;
+use App\Services\RoleMenuService;
+
+/**
+ * 菜单
+ */
+class MenuService extends BaseService
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */
+    public static function model(): string
+    {
+        return Menu::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+       
+        if (isset($search['id']) && !empty($search['id'])) {
+            $where[] = ['id', '=', $search['id']];
+        }
+        if (isset($search['parent_id']) && !empty($search['parent_id'])) {
+            $where[] = ['parent_id', '=', $search['parent_id']];
+        }
+        if (isset($search['type']) && !empty($search['type'])) {
+            $where[] = ['type', '=', $search['type']];
+        }
+        if (isset($search['uri']) && !empty($search['uri'])) {
+            $where[] = ['uri', '=', $search['uri']];
+        }
+        if (isset($search['status']) && !empty($search['status'])) {
+            $where[] = ['status', '=', $search['status']];
+        }
+        if (isset($search['title']) && !empty($search['title'])) {
+            $where[] = ['title', 'like', '%'.$search['title'].'%'];
+        }
+       
+        return $where;
+    }
+
+    /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?Menu
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 15;
+        $paginator = self::model()::where(self::getWhere($search))
+            ->orderBy("sort", 'asc')
+            ->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+    /**
+     * @description: 
+     * @param {*} $params
+     * @return {*}
+     */    
+    public static function submit($params = [])
+    {
+        $result = false;
+        $msg['code'] = self::NOT;
+        $msg['msg'] = '';
+        // 2. 判断是否是更新
+        if (!empty($params['id'])) {
+            // 更新
+            $info = self::findOne(['id'=>$params['id']] );
+            if (!$info) {
+                $msg['msg'] = '菜单不存在!';
+            }else{
+                $result = $info->update($params);
+            }
+
+        } else {
+            // 创建
+            $result = $info = self::model()::create($params);
+        }
+
+
+        if($result){
+           $msg['code'] = self::YES;
+           $msg['msg'] = '设置成功';
+        }else{
+            $msg['msg'] = empty($msg['msg']) ?'操作失败':$msg['msg'];
+        }
+
+        return $msg;
+    }
+
+    /**
+     * @description: 获取菜单树
+     * @return {*}
+     */
+    public static function getTree()
+    {
+        $list = self::findAll(['status' => self::model()::STATUS_SHOW]);
+        $tree = self::toTree($list,0,0,'parent_id');
+        return $tree;
+    }
+
+    /**
+     * @description: 
+     * @param {*} $userId
+     * @param {*} $type
+     * @return {*}
+     */    
+    public static function getUserMenu($userId ,$type = 1)
+    {
+        // 超级管理账号直接查看所有的菜单
+        if($userId == 1){
+            $list = self::findAll(['type' => $type ,'status' => self::model()::STATUS_SHOW]);
+        }else{
+            $roleIds = RoleUserService::model()::where('user_id',$userId)->pluck('role_id')->toArray();
+       
+            $roles = RoleService::model()::with(['menus'=> function ($query) use ($type) {
+                $query->where('type', $type);
+            }])->whereIn('id',$roleIds)->get();
+            $allMenus = [];
+
+            foreach ($roles as $role) {
+                if (!empty($role['menus'])) {
+                    $allMenus = array_merge($allMenus, $role['menus']->toArray());
+                }
+            }
+            $list = $allMenus;
+        }
+
+        $tree = self::toTree($list,0,0,'parent_id');
+        return $tree;
+    }
+
+    /**
+     * @description: 校验按钮权限
+     * @param {*} $userId
+     * @param {*} $uri
+     * @return {*}
+     */    
+    public static function checkMenu($userId ,$uri)
+    {
+        if($userId == 1){
+            return true;
+        }else{
+            $info = self::findOne(['uri' => $uri ,'status' => self::model()::STATUS_SHOW ,'type' => self::model()::TYPE_BUTTON]);
+            if($info){
+                $menuId = $info->id;
+                $roleIds = RoleUserService::model()::where('user_id',$userId)->pluck('role_id')->toArray();
+                $result = RoleMenuService::model()::whereIn('role_id', $roleIds)
+                     ->where('menu_id', $menuId)
+                     ->count();
+               
+                if($result){
+                    return true;
+                }else{
+                    return false;
+                }
+            }else{
+                return true;
+            }
+        }
+        
+    }
+}

+ 194 - 0
app/Services/OnLineService.php

@@ -0,0 +1,194 @@
+<?php
+
+
+namespace App\Services;
+
+
+use App\Constants\StepStatus;
+use App\Models\Room;
+use App\Models\RoomUser;
+use App\Models\User;
+use App\Models\Wallet;
+use Illuminate\Support\Facades\Cache;
+
+class OnLineService
+{
+
+    static function join($chatId, $roomId, $messageId)
+    {
+        $room = Room::where('room_id', $roomId)->first();
+        if (!$room) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => '❌ 房间不存在,请重新输入!',
+                'reply_to_message_id' => $messageId
+            ]];
+        }
+        if ($room->status !== 1 && $room->status != 2) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => '❌ 房间已开始游戏,请重新输入!',
+                'reply_to_message_id' => $messageId
+            ]];
+        }
+
+        if($room->status == 2 && !$room->midway){
+            return [[
+                'chat_id' => $chatId,
+                'text' => '❌ 房间已开始游戏,不允许中途加入!',
+                'reply_to_message_id' => $messageId
+            ]];
+        }
+        $wallet = Wallet::where('member_id', $chatId)->first();
+        $zuiDi = $room->base_score * 100;
+        if ($wallet->available_balance < $zuiDi) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => "❌您选择的是:{$room->base_score}USDT的房间,最低余额:{$zuiDi}USDT\n请重新输入或充值余额!",
+                'reply_to_message_id' => $messageId
+            ]];
+        }
+        if ($room->join_count >= $room->participants) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => '❌加入失败,房间人数已满'
+            ]];
+        }
+        $ru = RoomUser::where('member_id', $chatId)
+            ->whereIn('status', [0, 1, 2, 3])
+            ->first();
+        if ($ru) {
+            $keyboard = [
+                [
+                    ['text' => '进入房间', 'callback_data' => "games@@home{$ru->room_id}"],
+                ],
+            ];
+            return [[
+                'chat_id' => $chatId,
+                'text' => "您已加入房间,请进入房间继续游戏",
+                'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+            ]];
+        }
+
+        $user = User::where('member_id', $chatId)->first();
+        $ru = new RoomUser();
+        $ru->room_id = $room->room_id;
+        $ru->member_id = $chatId;
+        $ru->first_name = $user->first_name;
+        $ru->status = 0;
+        $ru->save();
+        $room->join_count += 1;
+        $room->save();
+        $arr = [];
+        $list = RoomUser::where('room_id', $ru->room_id)->where('member_id', '<>', $chatId)->get();
+        foreach ($list as $item) {
+            $arr[] = [
+                'chat_id' => $item->member_id,
+                'text' => "新用户进入房间",
+            ];
+            if ($room->join_count == $room->participants) {
+                $arr[] = [
+                    'chat_id' => $item->member_id,
+                    'text' => "房间已满员,请所有玩家准备",
+                ];
+            }
+        }
+
+
+
+        $data = RoomService::getGameIdMessage($chatId, $room->game_name, null);
+
+        unset($data['message_id']);
+        $arr[] = $data;
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_GAME_ID);
+        return $arr;
+    }
+
+    static function joinRoom($chatId, $messageId)
+    {
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_JOIN_ROOM_ID);
+        return [
+            'chat_id' => $chatId,
+            'message_id' => $messageId,
+            'text' => '请发送您要加入的房间号'
+        ];
+    }
+
+    public function getList($chatId, $messageId = null, $page = 1, $limit = 8)
+    {
+        Cache::delete("{$chatId}_CREATE_ROOM_MESSAGE_ID");
+        $ru = RoomUser::where('member_id', $chatId)
+            ->whereIn('status', [0, 1, 2, 3])
+            ->first();
+        if ($ru) {
+            $keyboard = [
+                [
+                    ['text' => '进入房间', 'callback_data' => "games@@home{$ru->room_id}"],
+                ],
+            ];
+            return [
+                'chat_id' => $chatId,
+                'text' => "您已加入房间,请进入房间继续游戏",
+                'message_id' => $messageId,
+                'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+            ];
+        }
+
+        $res = Room::where('status', 1)
+            ->forPage($page, $limit)->get();
+        $count = Room::where('status', 1)->count();
+//        if ($count > 0) {
+        $keyboard = [];
+        foreach ($res as $item) {
+            $keyboard[] = [
+                ['text' => "{$item->game_name}  人数:{$item->join_count}/$item->participants  局数:{$item->rounds}局  底分:{$item->base_score}USDT", 'callback_data' => "join@@{$item->room_id}"]
+            ];
+        }
+        $dPage = false;
+        if ($page > 1) {
+            $dPage = true;
+            $keyboard[] = [
+                ['text' => "👆上一页", 'callback_data' => "onlineNextPage@@" . ($page - 1)]
+            ];
+        }
+        $allPage = ceil($count / $limit);
+        if ($allPage > $page) {
+            $dPage = true;
+            if ($page > 1) {
+                $keyboard[count($keyboard) - 1][] = ['text' => "👇下一页", 'callback_data' => "onlineNextPage@@" . ($page + 1)];
+            } else {
+                $keyboard[] = [
+                    ['text' => "👇下一页", 'callback_data' => "onlineNextPage@@" . ($page + 1)]
+                ];
+            }
+        }
+        if ($dPage) {
+
+            if (count($keyboard[count($keyboard) - 1]) >= 2) {
+                $keyboard[count($keyboard) - 1][] = ['text' => '输入房间号', 'callback_data' => 'join@@room'];
+                $keyboard[count($keyboard) - 1][] = ['text' => "🔙返回", 'callback_data' => "del@@message"];
+            } else {
+                $keyboard[] = [
+                    ['text' => '输入房间号', 'callback_data' => 'join@@room'],
+                    ['text' => "🔙返回", 'callback_data' => "del@@message"],
+                ];
+            }
+        } else {
+            $keyboard[] = [
+                ['text' => '输入房间号', 'callback_data' => 'join@@room'],
+                ['text' => "🔙返回", 'callback_data' => "del@@message"],
+            ];
+        }
+
+
+        $data = [
+            'chat_id' => $chatId,
+            'text' => "👇选择以下在线房间,加入后可参与游戏!",
+            'message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+        return $data;
+//        }
+
+    }
+}

+ 186 - 0
app/Services/RechargeService.php

@@ -0,0 +1,186 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Services\BaseService;
+use App\Models\Recharge;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Cache;
+use App\Services\WalletService;
+use App\Services\CollectService;
+use App\Services\UserService;
+use App\Services\BalanceLogService;
+use App\Helpers\TronHelper;
+
+/**
+ * 用户充值记录
+ */
+class RechargeService extends BaseService
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */
+    public static function model(): string
+    {
+        return Recharge::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+        if (isset($search['coin']) && !empty($search['coin'])) {
+            $where[] = ['coin', '=', $search['coin']];
+        }
+        if (isset($search['net']) && !empty($search['net'])) {
+            $where[] = ['net', '=', $search['net']];
+        }
+        if (isset($search['to_address']) && !empty($search['to_address'])) {
+            $where[] = ['to_address', '=', $search['to_address']];
+        }
+        if (isset($search['id']) && !empty($search['id'])) {
+            $where[] = ['id', '=', $search['id']];
+        }
+        if (isset($search['member_id']) && !empty($search['member_id'])) {
+            $where[] = ['member_id', '=', $search['member_id']];
+        }
+        if (isset($search['txid']) && !empty($search['txid'])) {
+            $where[] = ['txid', '=', $search['txid']];
+        }
+        if (isset($search['status']) && $search['status'] != '') {
+            $where[] = ['status', '=', $search['status']];
+        }
+        if (isset($search['type']) && $search['type'] != '') {
+            $where[] = ['type', '=', $search['type']];
+        }
+        return $where;
+    }
+
+    /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?Recharge
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 15;
+        $paginator = self::model()::where(self::getWhere($search))
+            ->orderBy("created_at", 'desc')
+            ->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+
+    /**
+     * @description: 同步会员的USDT充值记录
+     * @param {*} $memberId
+     * @return {*}
+     */
+    public static function syncUsdtRechargeRecords($memberId)
+    {
+        $walletInfo = WalletService::findOne(['member_id' => $memberId]);
+        $data = TronHelper::getTrc20UsdtRecharges($walletInfo->address);
+        
+        foreach ($data as $k => $v) {
+            $v['member_id'] = $memberId;
+            $v['net'] = $walletInfo->net;
+            $V['type'] = self::model()::TYPE_AUTO;
+            $v['created_at'] = now();
+            $v['updated_at'] = now();
+            $data[$k] = $v;
+        }
+        $m = new (self::model());
+        $result = $m->insertOrIgnore($data);
+        return $result;
+    }
+
+    /**
+     * @description: 充值确认
+     * @param {*} $txid
+     * @return {*}
+     */
+    public static function handleRechargeConfirmation($txid)
+    {
+        $info = self::findOne(['txid' => $txid]); // 获取充值的信息
+
+        // 待处理进行充值
+        if ($info['status'] == self::model()::STATUS_STAY) {
+            $result = TronHelper::getTransactionConfirmations($txid);
+
+            if ($result['success']) {
+                $data = [];
+                $data['block_height'] = $result['block_number'];
+                $data['confirmations'] = $result['latest_block'] - $result['block_number'];
+
+                if ($data['confirmations'] >= TronHelper::CONFIRMED_NUMBER) {
+                    $data['status'] = self::model()::STATUS_SUCCESS;
+                    $where = self::getWhere(['txid' => $txid, 'status' => self::model()::STATUS_STAY]);
+                    $recharge = self::model()::where($where)->update($data);
+
+                    // 更新成功,变动可用余额
+                    if ($recharge) {
+                        $balanceData = WalletService::updateBalance($info->member_id, $info->amount);
+                        BalanceLogService::addLog($info->member_id, $info->amount, $balanceData['before_balance'], $balanceData['after_balance'], '充值', $info->id, '');
+
+                        CollectService::createCollect($info->to_address, $info->coin, $info->net);
+
+                        $amount = floatval($info->amount);
+                        $text = "  ➕ 充币到帐 +{$amount} USDT\n";
+                        $text .= "链上哈希:{$txid}\n";
+                        $text .= "当前余额:{$balanceData['after_balance']}\n";
+                        TopUpService::notifyTransferSuccess($info->member_id, $text);
+                    }
+                }
+            }
+        }
+
+    }
+
+    /**
+     * @description: 同步待处理的充值记录
+     * @return {*}
+     */
+    public static function syncRechargeStay()
+    {
+        $list = self::findAll(['status' => self::model()::STATUS_STAY ,'type' => self::model()::TYPE_AUTO]);
+        foreach ($list as $k => $v) {
+            self::handleRechargeConfirmation($v->txid);
+        }
+    }
+}

+ 64 - 0
app/Services/RecordService.php

@@ -0,0 +1,64 @@
+<?php
+
+
+namespace App\Services;
+
+//战绩相关服务
+use App\Models\RoomUser;
+
+class RecordService
+{
+    public function getList($chatId, $firstName, $messageId = null, $page = 1, $limit = 4)
+    {
+        $list = RoomUser::where('status', 4)
+            ->where('member_id', $chatId)
+            ->with('room:room_id,game_name,base_score,participants,rounds')
+            ->forPage($page, $limit)
+            ->orderBy('updated_at', 'desc')
+            ->get();
+        $count = RoomUser::where('status', 4)
+            ->where('member_id', $chatId)
+            ->count();
+
+        $test = "👤 {$firstName}  战绩记录\n\n";
+        foreach ($list as $item) {
+            $test .= "游戏:{$item->room->game_name}   {$item->room->participants}人   {$item->room->rounds}局   房间号:{$item->room_id}\n";
+            $test .= "底分:{$item->room->base_score} USDT\n";
+            $test .= "得分:{$item->score}\n";
+            $temp = floatval($item->real_score);
+            $test .= "盈亏:{$temp} USDT\n";
+            $temp = floatval($item->brokerage);
+            if ($temp > 0) $test .= "抽佣:{$temp} USDT\n";
+            $test .= "日期:{$item->updated_at}\n";
+            $test .= "-----------------------------------------\n";
+
+        }
+
+        if ($page > 1) {
+            $keyboard[] = [
+                ['text' => "👆上一页", 'callback_data' => "recordNextPage@@" . ($page - 1)]
+            ];
+        }
+        $allPage = ceil($count / $limit);
+        if ($allPage > $page) {
+            if ($page > 1) {
+                $keyboard[count($keyboard) - 1][] = ['text' => "👇下一页", 'callback_data' => "recordNextPage@@" . ($page + 1)];
+            } else {
+                $keyboard[] = [
+                    ['text' => "👇下一页", 'callback_data' => "recordNextPage@@" . ($page + 1)]
+                ];
+            }
+        }
+
+        $keyboard[] = [
+            ['text' => "返回", 'callback_data' => "message@@close"]
+        ];
+        return [
+            'chat_id' => $chatId,
+            'text' => $test,
+            'message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard]),
+
+        ];
+    }
+}

+ 145 - 0
app/Services/RoleMenuService.php

@@ -0,0 +1,145 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Services\BaseService;
+use App\Models\RoleMenu;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Cache;
+
+
+/**
+ * 角色菜单
+ */
+class RoleMenuService extends BaseService
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */
+    public static function model(): string
+    {
+        return RoleMenu::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+       
+        if (isset($search['role_id']) && !empty($search['role_id'])) {
+            $where[] = ['role_id', '=', $search['role_id']];
+        }
+        if (isset($search['menu_id']) && !empty($search['menu_id'])) {
+            $where[] = ['menu_id', '=', $search['menu_id']];
+        }
+       
+        return $where;
+    }
+
+    /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?RoleMenu
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 15;
+        $paginator = self::model()::where(self::getWhere($search))
+            // ->orderBy("sort", 'asc')
+            ->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+    /**
+     * @description: 
+     * @param {*} $params
+     * @return {*}
+     */    
+    public static function submit($role_id, $menus)
+    {
+        $result = false;
+
+        // 获取数据库中已存在的菜单列表(提取 menu_id)
+        $list = self::findAll(['role_id' => $role_id]);
+        $existIds = $list->pluck('menu_id')->toArray();
+
+        // 计算新增的菜单
+        $toInsert = array_diff($menus, $existIds);
+
+        // 计算需要删除的菜单
+        $toDelete = array_diff($existIds, $menus);
+
+       
+        // 开始数据库事务(假设有 DB 支持事务操作)
+        // DB::beginTransaction();
+        // try {
+            if(empty($menus)){
+                self::model()::where('role_id',$role_id)->delete();
+            }else{
+                // 批量插入新增菜单
+                if (!empty($toInsert)) {
+                    $insertData = [];
+                    foreach ($toInsert as $menu_id) {
+                        $insertData[] = [
+                            'menu_id' => $menu_id,
+                            'role_id' => $role_id,
+                        ];
+                    }
+                    self::model()::insert($insertData);
+                }
+
+                // 删除多余的菜单
+                if (!empty($toDelete)) {
+                    self::model()::where('role_id', $role_id)
+                        ->whereIn('menu_id', $toDelete)
+                        ->delete();
+                }
+
+            }
+            
+            // DB::commit();
+            $result = true;
+        // } catch (\Exception $e) {
+        //     DB::rollBack();
+        //     // 记录错误日志或抛出异常
+        // }
+
+        return $result;
+    }
+}

+ 147 - 0
app/Services/RoleService.php

@@ -0,0 +1,147 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Services\BaseService;
+use App\Models\Role;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Cache;
+use App\Services\RoleMenuService;   
+
+
+/**
+ * 菜单
+ */
+class RoleService extends BaseService
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */
+    public static function model(): string
+    {
+        return Role::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+       
+        if (isset($search['id']) && !empty($search['id'])) {
+            $where[] = ['id', '=', $search['id']];
+        }
+        if (isset($search['ids']) && !empty($search['ids'])) {
+            $where[] = ['id', 'in', $search['ids']];
+        }
+        if (isset($search['name']) && !empty($search['name'])) {
+            $where[] = ['name', '=', $search['name']];
+        }
+        if (isset($search['display_name']) && !empty($search['display_name'])) {
+            $where[] = ['display_name', 'like', '%'.$search['display_name'].'%'];
+        }
+       
+        return $where;
+    }
+
+    /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?Role
+    {
+        return self::model()::with(['menus'])->where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        
+        return self::model()::with(['menus'])->where(self::getWhere($search))->get();
+        
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 15;
+        $paginator = self::model()::with(['menus'])->where(self::getWhere($search))
+            // ->orderBy("sort", 'asc')
+            ->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+    /**
+     * @description: 
+     * @param {*} $params
+     * @return {*}
+     */    
+    public static function submit($params = [])
+    {
+        $result = false;
+        $msg['code'] = self::NOT;
+        $msg['msg'] = '';
+        
+        // 2. 判断是否是更新
+        if (!empty($params['id'])) {
+            // 更新
+            $info = self::findOne(['id'=>$params['id']] );
+            if (!$info) {
+                $msg['msg'] = '角色不存在!';
+            }else{
+                $result = $info->update($params);
+                $id = $params['id'];
+            }
+
+        } else {
+            // 创建
+            $result = $info = self::model()::create($params);
+            $id = $result->id;
+        }
+        
+       
+        if($result){
+            if(is_array($params['menus_ids'])){
+                $menus = $params['menus_ids'];
+            }else{
+                if(empty($params['menus_ids'])){
+                    $menus = [];
+                }else{
+                    $menus = explode(',',$params['menus_ids']);
+                }
+                
+            }
+            RoleMenuService::submit($id,$menus);
+           $msg['code'] = self::YES;
+           $msg['msg'] = '设置成功';
+        }else{
+            $msg['msg'] = empty($msg['msg']) ?'操作失败':$msg['msg'];
+        }
+
+        return $msg;
+    }
+}

+ 145 - 0
app/Services/RoleUserService.php

@@ -0,0 +1,145 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Services\BaseService;
+use App\Models\RoleUser;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Cache;
+
+
+/**
+ * 用户角色
+ */
+class RoleUserService extends BaseService
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */
+    public static function model(): string
+    {
+        return RoleUser::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+       
+        if (isset($search['role_id']) && !empty($search['role_id'])) {
+            $where[] = ['role_id', '=', $search['role_id']];
+        }
+        if (isset($search['user_id']) && !empty($search['user_id'])) {
+            $where[] = ['user_id', '=', $search['user_id']];
+        }
+       
+        return $where;
+    }
+
+    /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?RoleUser
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 15;
+        $paginator = self::model()::where(self::getWhere($search))
+            // ->orderBy("sort", 'asc')
+            ->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+    /**
+     * @description: 
+     * @param {*} $params
+     * @return {*}
+     */    
+    public static function submit($user_id, $roles)
+    {
+        $result = false;
+
+        // 获取数据库中已存在的角色列表(提取 role_id)
+        $list = self::findAll(['user_id' => $user_id]);
+        $existRoleIds = $list->pluck('role_id')->toArray();
+
+        // 计算新增的角色
+        $toInsert = array_diff($roles, $existRoleIds);
+
+        // 计算需要删除的角色
+        $toDelete = array_diff($existRoleIds, $roles);
+
+       
+        // 开始数据库事务(假设有 DB 支持事务操作)
+        // DB::beginTransaction();
+        // try {
+            if(empty($roles)){
+                self::model()::where('user_id',$user_id)->delete();
+            }else{
+                // 批量插入新增角色
+                if (!empty($toInsert)) {
+                    $insertData = [];
+                    foreach ($toInsert as $role_id) {
+                        $insertData[] = [
+                            'user_id' => $user_id,
+                            'role_id' => $role_id,
+                        ];
+                    }
+                    self::model()::insert($insertData);
+                }
+
+                // 删除多余的角色
+                if (!empty($toDelete)) {
+                    self::model()::where('user_id', $user_id)
+                        ->whereIn('role_id', $toDelete)
+                        ->delete();
+                }
+
+            }
+            
+            // DB::commit();
+            $result = true;
+        // } catch (\Exception $e) {
+        //     DB::rollBack();
+        //     // 记录错误日志或抛出异常
+        // }
+
+        return $result;
+    }
+}

+ 1007 - 0
app/Services/RoomService.php

@@ -0,0 +1,1007 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Constants\StepStatus;
+use App\Models\Config;
+use App\Models\Game;
+use App\Models\Room;
+use App\Models\RoomUser;
+use App\Models\User;
+use App\Models\UserGame;
+use App\Models\Wallet;
+use Telegram\Bot\Api;
+use Telegram\Bot\Exceptions\TelegramSDKException;
+use Illuminate\Support\Facades\Cache;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Log;
+
+//房间服务
+class RoomService
+{
+
+    public static function model(): string
+    {
+        return Room::class;
+    }
+
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+        if (isset($search['id']) && !empty($search['id'])) {
+            $where[] = ['id', '=', $search['id']];
+        }
+
+
+        if (isset($search['member_id']) && !empty($search['member_id'])) {
+            $where[] = ['member_id', '=', $search['member_id']];
+        }
+
+        if (isset($search['room_id']) && !empty($search['room_id'])) {
+            if(isset($search['like_room_id']) && $search['like_room_id'] == true){
+                $where[] = ['room_id', 'like', '%' . $search['room_id'] . '%'];
+
+            }else{
+                $where[] = ['room_id', '=', $search['room_id']];
+
+            }
+        }
+        if (isset($search['old_room_id']) && !empty($search['old_room_id'])) {
+            $where[] = ['old_room_id', '=', $search['old_room_id']];
+        }
+        if (isset($search['base_score']) && !empty($search['base_score'])) {
+            $where[] = ['base_score', '=', $search['base_score']];
+        }
+
+        if (isset($search['not_status']) && $search['not_status'] != '') {
+            $where[] = ['status', '<>', $search['not_status']];
+        }
+
+        if (isset($search['status']) && $search['status'] != '') {
+            $where[] = ['status', '=', $search['status']];
+        }
+
+
+        return $where;
+    }
+
+    public static function findOne(array $search): ?Room
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 10;
+        $paginator = self::model()::where(self::getWhere($search))
+            ->orderBy('created_at', 'desc')
+            ->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+
+    private $telegram;
+
+    public function __construct(Api $tg)
+    {
+        $this->telegram = $tg;
+    }
+
+    public static function getGameIdMessage($chatId, $gameName, $messageId)
+    {
+        $text = "请设置游戏ID\n";
+        $keyboard = [];
+        $ug = UserGame::where('member_id', $chatId)
+            ->where('game_name', $gameName)->first();
+        if ($ug) {
+            $text = "游戏ID:{$ug->game_id}\n";
+            $text .= "-------------------\n";
+            $text .= "请确认或直接输入新的游戏ID\n";
+            $keyboard[] = [['text' => '确认', 'callback_data' => "inputGameID@@{$ug->game_id}"]];
+        }
+        $text .= "❓如何获取我的ID\n";
+        $text .= "1.登录微信小程序:如 财神十三张\n";
+        $text .= "2.登录成功后,点击用户头像,查看我的资料\n";
+        $text .= "3.复制对应的ID\n";
+        $data = [
+            'chat_id' => $chatId,
+            'text' => $text
+        ];
+        if ($messageId) {
+            $data['message_id'] = $messageId;
+            Cache::put("{$chatId}_GAME_ID_MESSAGE_ID", $messageId);
+        }
+
+        if ($keyboard) {
+            $data['reply_markup'] = json_encode(['inline_keyboard' => $keyboard]);
+        }
+        return $data;
+    }
+
+    //提前开始游戏
+    public function start($chatId, $roomId, $messageId)
+    {
+        $room = Room::where('room_id', $roomId)->first();
+        if ($room->status != 1) {
+            return [['chat_id' => $chatId, 'text' => '已经开始,无需重复开始', 'message_id' => $messageId]];
+        }
+
+        $list = RoomUser::where('room_id', $roomId)->get();
+        if (count($list) < 2) {
+            return [['chat_id' => $chatId, 'text' => '最少需要2人才可以开始游戏']];
+        }
+        $readyCount = RoomUser::where('room_id', $roomId)->where('status', 1)->count();
+        if ($readyCount != count($list)) {
+            return [['chat_id' => $chatId, 'text' => '还有人员未准备,请等待所有人员准备']];
+        }
+
+        $room->status = 2;// 游戏中
+        $room->save();
+        RoomUser::where('room_id', $roomId)->update(['status' => 2]);//游戏中
+
+        
+        foreach ($list as $item) {
+            $text = "✅ 房主提前开始游戏 \n";
+            $text .= "房号 {$room->room_id} \n";
+            $text .= "‼️请回到微信小程序搜索《{$room->game_name}》\n输入房间号({$room->room_id})加入游戏\n";
+
+            $arr[] = [
+                'chat_id' => $item->member_id,
+                'text' => $text
+            ];
+            if ($item->member_id == $chatId) {
+                $arr[] = $this->gameHome($roomId, $item->member_id, $messageId);
+            } else {
+                $arr[] = $this->gameHome($roomId, $item->member_id);
+            }
+
+        }
+        return $arr;
+
+    }
+
+    //返回游戏首页
+    public function gameHome($roomId, $chatId, $messageId = null)
+    {
+        $ru = RoomUser::where('member_id', $chatId)
+            ->where('room_id', $roomId)->first();
+        if (!$ru->game_id) {
+            $room = Room::where('room_id', $roomId)->first();
+            $data = RoomService::getGameIdMessage($chatId, $room->game_name, $messageId);
+            Cache::put(get_step_key($chatId), StepStatus::INPUT_GAME_ID);
+            return $data;
+        }
+
+
+        $room = Room::where('room_id', $roomId)->first();
+        $data = $this->getGameInfo($room, $chatId, $room->member_id == $chatId);
+        $data['chat_id'] = $chatId;
+        if ($messageId != null) $data['message_id'] = $messageId;
+        Cache::delete(get_step_key($chatId));
+        return $data;
+    }
+
+    //获取最终的房间信息和确认按钮
+    private function getCreateButton($room, $chatId)
+    {
+        $text = "房间信息\n";
+        $text .= "🗂 {$room->game_name}   {$room->participants}人   {$room->rounds}局\n";
+        $text .= "房间号:{$room->room_id}\n";
+        $text .= "底分:{$room->base_score} USDT\n";
+        $text .= "中途加入:".($room->midway?'允许':'不允许')." \n";
+        $text .= "\n";
+        $keyboard[] = [
+            ['text' => '创建', 'callback_data' => 'room@@done'],
+            ['text' => '❌取消', 'callback_data' => "games@@cancel"]
+        ];
+
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+    /**
+     * 获取游戏房间信息和房间的操作按钮
+     * @param $roomId string 房间ID
+     * @param $isHouseOwner bool 是否是房主
+     *
+     */
+    public function getGameInfo($room, $chatId, $isHouseOwner, $text = '')
+    {
+        if($isHouseOwner){
+            $text .= "游戏:{$room->game_name}   {$room->rounds}局  房号:{$room->room_id} \n";
+        }else{
+            $text .= "游戏:{$room->game_name}   {$room->rounds}局 \n";
+
+        }
+        $text .= "底分:{$room->base_score} USDT\n";
+        $text .= "中途加入:".($room->midway?'允许':'不允许')." \n";
+        $text .= "人数:{$room->participants}人\n";
+        $text .= "{$room->introduction}\n";
+        $text .= "\n";
+        $text .= "----------------------------------------------\n";
+        $text .= "\n";
+        $text .= "‼️1.开始游戏前,非房主请点击准备,房主等待\n大家准备后,点击开始游戏。\n";
+        $text .= "‼️2.游戏结束后,请务必上传双方和第三方的战\n绩截图。如出现争议,平台将以三方截图为依据\n进行审核,以确保比赛的公平与公正\n";
+        // $text .= "‼️3.请回到微信小程序搜索《{$room->game_name}》\n输入房间号({$room->room_id})加入游戏\n";
+        $keyboard = $this->getGameKeyboard($room->room_id, $chatId, $isHouseOwner);
+        return [
+            'text' => $text,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+
+    //获取选择底分的按钮列表
+    private function getChooseBaseScoreMsg()
+    {
+        $config = Config::where('field', 'base_score')->first();
+        $baseScore = json_decode($config->val, true);
+        $keyboard = [];
+        foreach ($baseScore as $item) {
+            if (count($keyboard) == 0) {
+                $keyboard = [[
+                    ['text' => "💵 {$item} USDT", 'callback_data' => "roomMin@@" . $item],
+                ]];
+            } else {
+                if (count($keyboard[count($keyboard) - 1]) >= 3) $keyboard[] = [];
+                $keyboard[count($keyboard) - 1][] = ['text' => "💵 {$item} USDT", 'callback_data' => "roomMin@@" . $item];
+            }
+        }
+        return $keyboard;
+    }
+
+    private function getGameKeyboard($roomId, $chatId, $isHouseOwner)
+    {
+        $keyboard = [
+            [
+                ['text' => '准备', 'callback_data' => "games@@ready{$roomId}"],
+                ['text' => '人员/状态', 'callback_data' => "games@@status{$roomId}"],
+            ],
+        ];
+        $ru = RoomUser::where('room_id', $roomId)->where('member_id', $chatId)
+            ->first();
+        if ($ru->status != 0) {
+            $keyboard[0][0]['text'] = "✅ 已准备";
+            $keyboard[0][0]['callback_data'] = "games@@readyS";
+        }
+        $room = Room::where('room_id', $roomId)->first();
+        if ($room->status == 2) {
+            if (in_array($ru->status, [2, 3])) {
+                $keyboard[] = [['text' => '结算游戏', 'callback_data' => "games@@settle{$roomId}"]];
+            } else if (in_array($ru->status, [0, 1])) {
+                $keyboard[] = [['text' => '退出', 'callback_data' => "games@@exit{$roomId}"]];
+            }
+        } else if (in_array($room->status, [0, 1])) {
+            if ($isHouseOwner) {
+                $keyboard[] = [
+                    ['text' => '解散', 'callback_data' => "games@@Disband{$roomId}"],
+                    ['text' => '开始游戏', 'callback_data' => "games@@start{$roomId}"],
+                ];
+            } else {
+                $keyboard[] = [['text' => '退出', 'callback_data' => "games@@exit{$roomId}"]];
+            }
+        }
+        return $keyboard;
+    }
+
+
+    //人员状态
+    public function userStatus($roomId, $chatId, $messageId)
+    {
+        $list = RoomUser::where('room_id', $roomId)
+            ->orderBy('id')
+            ->get();
+        if (count($list) == 0) {
+            return [
+                'chat_id' => $chatId,
+                'text' => '房间不存在,请先创建房间'
+            ];
+        }
+        $status = ['待准备', '已准备', '游戏中', '待结算', '已结算'];
+        $text = "编号      角色          状态          游戏ID\n";
+
+        foreach ($list as $index => $item) {
+            $no = $index + 1;
+            $gameId = $item->game_id;
+            if (!$gameId) $gameId = '-------';
+            if ($no == 1) {
+                $text .= "{$no}            房主          {$status[$item->status]}      {$gameId}\n";
+//                $text .= "{$no}\t\t{$item->game_id}\t\t{$status[$item->status]}\t\t房主\n";
+            } else {
+                $text .= "{$no}            玩家          {$status[$item->status]}      {$gameId}\n";
+//                $text .= "{$no}\t\t{$item->game_id}\t\t{$status[$item->status]}\t\t玩家\n";
+            }
+        }
+
+        $keyboard = [
+            [
+                ['text' => '返回', 'callback_data' => "games@@home{$roomId}"],
+            ],
+        ];
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'parse_mode' => 'Markdown',
+            'message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+
+        ];
+    }
+
+    //准备
+    public function ready($roomId, $chatId, $messageId)
+    {
+        $ru = RoomUser::whereIn('status', [0, 1, 2, 3])
+            ->where('member_id', $chatId)
+            ->where('room_id', $roomId)
+            ->first();
+        if (!$ru) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => "❌您还没有选择房间,请先通过在线房间进入房间"
+            ]];
+        }
+        if (in_array($ru->status, [2, 3])) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => "❌您已在游戏中,无需准备"
+            ]];
+        }
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_GAME_ID);
+        if (!$ru->game_id) {
+            $room = Room::where('room_id', $roomId)->first();
+            $data = RoomService::getGameIdMessage($chatId, $room->game_name, $messageId);
+            return [$data];
+        }
+
+
+       
+        $room = Room::where('room_id', $roomId)->first();
+        $ru->status = 1;
+        $arr = [];
+        if ($room->status == 2) {
+            $ru->status = 2;
+            $text = "✅ 游戏已开始 \n";
+            $text .= "房号 {$room->room_id} \n";
+            $text .= "‼️请回到微信小程序搜索《{$room->game_name}》\n输入房间号({$room->room_id})加入游戏\n";
+            $arr[] = [
+                'chat_id' => $chatId,
+                'text' => $text
+            ];
+        }
+        $ru->save();
+        $ready = RoomUser::where('room_id', $roomId)->where('status', 1)->count();
+        
+
+        $data = $this->getGameInfo($room, $chatId, $room->member_id == $chatId);
+        $data['chat_id'] = $chatId;
+        $data['message_id'] = $messageId;
+        $arr[] = $data;
+        if ($room->participants == $ready) {
+            $list = RoomUser::where('room_id', $roomId)->get();
+            $room->status = 2;// 游戏中
+            $room->save();
+            RoomUser::where('room_id', $roomId)->update(['status' => 2]);//游戏中
+            foreach ($list as $item) {
+                $text = "✅ 所有人员都已准备,自动开始游戏 \n";
+                $text .= "房号 {$room->room_id} \n";
+                $text .= "‼️请回到微信小程序搜索《{$room->game_name}》\n输入房间号({$room->room_id})加入游戏\n";
+                $arr[] = [
+                    'chat_id' => $item->member_id,
+                    'text' => $text
+                ];
+                if ($item->member_id == $chatId) {
+                    $arr[] = $this->gameHome($roomId, $item->member_id, $messageId);
+                } else {
+                    $arr[] = $this->gameHome($roomId, $item->member_id);
+                }
+
+            }
+        }
+        return $arr;
+    }
+
+
+    //玩家加入房间
+    public function join($chatId, $roomId, $messageId = null)
+    {
+        $room = Room::where('room_id', $roomId)->first();
+        if (!$room) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => '❌ 房间已关闭,请重新选择房间!'
+            ]]; 
+        }
+
+        if ($room->status !== 1 && $room->status !== 2) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => '❌ 房间已开始游戏,请重新选择房间!'
+            ]];
+        }
+
+        if($room->status == 2 && !$room->midway){
+            return [[
+                'chat_id' => $chatId,
+                'text' => '❌ 房间已开始游戏,不允许中途加入!'
+            ]];
+
+        }
+        $wallet = Wallet::where('member_id', $chatId)->first();
+        $zuiDi = $room->base_score * 100;
+        if ($wallet->available_balance < $zuiDi) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => "❌您选择的是:{$room->base_score}USDT的房间,最低余额:{$zuiDi}USDT\n请重新选择或充值余额!"
+            ]];
+        }
+
+
+        $ru = RoomUser::whereIn('status', [0, 1, 2, 3])
+            ->where('member_id', $chatId)
+            ->first();
+        if ($ru) {
+            if ($room->join_count >= $room->participants && $ru->member_id != $chatId) {
+                return [[
+                    'chat_id' => $chatId,
+                    'text' => '❌加入失败,房间人数已满'
+                ]];
+            }
+
+
+            $text = '';
+            if ($room->room_id !== $ru->room_id) {
+                $text .= "❌加入失败,您已经加入了其他房间,\n以下是您加入的房间信息\n";
+                $text .= "\n";
+                $text .= "------------------------------\n";
+                $text .= "\n";
+                $room = Room::where('room_id', $ru->room_id)->first();
+            }
+            $data = $this->getGameInfo($room, $chatId, $room->member_id == $chatId, $text);
+            $data['chat_id'] = $chatId;
+            $data['message_id'] = $messageId;
+            return [$data];
+        }
+        $user = User::where('member_id', $chatId)->first();
+        $ru = new RoomUser();
+        $ru->room_id = $room->room_id;
+        $ru->member_id = $chatId;
+        $ru->first_name = $user->first_name;
+        $ru->status = 0;
+        $ru->save();
+        $room->join_count += 1;
+        $room->save();
+        $arr = [];
+        $list = RoomUser::where('room_id', $ru->room_id)->where('member_id', '<>', $chatId)->get();
+        foreach ($list as $item) {
+            $arr[] = [
+                'chat_id' => $item->member_id,
+                'text' => "新用户进入房间",
+            ];
+            if ($room->join_count == $room->participants) {
+                $arr[] = [
+                    'chat_id' => $item->member_id,
+                    'text' => "房间已满员,请所有玩家准备",
+                ];
+            }
+        }
+        $data = RoomService::getGameIdMessage($chatId, $room->game_name, $messageId);
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_GAME_ID);
+        $arr[] = $data;
+        return $arr;
+    }
+
+    /**
+     * 取消创建房间
+     * @param $chatId
+     * @throws TelegramSDKException
+     */
+    public function cancel($chatId, $messageId)
+    {
+        Room::where('status', 0)
+            ->where('member_id', $chatId)->delete();
+        return [
+            'chat_id' => $chatId,
+            'text' => '🚫游戏创建流程已取消',
+            'message_id' => $messageId
+        ];
+    }
+
+    public function exit($data, $chatId, $messageId)
+    {
+        $roomId = preg_replace('/^games@@exit/', '', $data);
+        $roomId = intval($roomId);
+
+        $room = Room::where('room_id', $roomId)->first();
+        if (!$room) return ['chat_id' => $chatId, 'text' => '房间已解散,无需退出'];
+
+        $ru = RoomUser::where('room_id', $roomId)
+            ->where('member_id', $chatId)->first();
+        if (!$ru) {
+            return ['chat_id' => $chatId, 'text' => '不在房间内,无需退出'];
+        }
+
+        if ($ru->member_id == $room->member_id) {
+            return ['chat_id' => $chatId, 'text' => '房主不可退出房间'];
+        }
+
+        if (!in_array($room->status, [1, 2]) || !in_array($ru->status, [0, 1])) {
+            return ['chat_id' => $chatId, 'text' => '游戏已开局,退出失败'];
+        }
+        $ru->delete();
+        $room->join_count -= 1;
+        $room->save();
+
+
+        $data = (new OnLineService())->getList($chatId, null, 1);
+        $data['message_id'] = $messageId;
+        return $data;
+//        return [
+//            'chat_id' => $chatId,
+//            'text' => "已退出房间",
+//            'message_id' => $messageId
+//        ];
+    }
+
+    /**
+     * 解散房间
+     * @return array
+     */
+    public function disband($roomId, $chatId, $messageId)
+    {
+        $room = Room::whereIn('status', [0, 1, 2, 3])
+            ->where('member_id', $chatId)
+            ->where('room_id', $roomId)
+            ->first();
+        if (!$room) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => '房间不存在,请先创建房间 /room',
+                'message_id' => $messageId
+            ]];
+        } else {
+            if (in_array($room->status, [2, 3])) {
+                return [[
+                    'chat_id' => $chatId,
+                    'text' => '游戏已开始,不可解散房间'
+                ]];
+            }
+
+            $list = RoomUser::where('room_id', $room->room_id)->get();
+            $arr = [];
+            foreach ($list as $item) {
+                $a = [
+                    'chat_id' => $item->member_id,
+                    'text' => "❌房主已解散房间",
+                ];
+                if ($item->member_id == $room->member_id) {
+                    $a['message_id'] = $messageId;
+                }
+                $arr[] = $a;
+            }
+            $room->delete();
+            RoomUser::where('room_id', $room->room_id)->delete();
+            return $arr;
+        }
+    }
+
+
+    /**
+     * 设置游戏ID
+     * @param $chatId string 用户的tg ID
+     * @param $array array 设置房间号的前缀【房间号】
+     * @throws TelegramSDKException
+     */
+    public function setGameID($chatId, $gameId, $messageId)
+    {
+        if (!preg_match('/^\d+$/', $gameId)) return [['chat_id' => $chatId, 'text' => '输入错误,请发送游戏ID', 'reply_to_message_id' => $messageId]];
+        $ru = RoomUser::where('member_id', $chatId)
+            ->where('status', 0)->first();
+        if (!$ru) return [["chat_id" => $chatId, 'text' => '设置失败,没有房间', 'reply_to_message_id' => $messageId]];
+        $room = Room::where('room_id', $ru->room_id)->whereIn('status', [0, 1, 2])->first();
+        if (!$room) return [["chat_id" => $chatId, 'text' => '设置失败,没有房间', 'reply_to_message_id' => $messageId]];
+
+        $ru->game_id = $gameId;
+        $ru->save();
+        if ($room->status != 2) {
+            $room->status = 1;
+            $room->save();
+        }
+
+
+        UserGame::updateOrCreate(
+            ['member_id' => $chatId, 'game_name' => $room->game_name],
+            ['game_id' => $gameId]
+        );
+
+        $notice = null;
+        $text = "";
+        if ($chatId == $room->member_id) {
+            $notice = ChannelService::createRoomNotice($room->room_id);
+            $text = "✅ 房间创建成功\n";
+            $text .= "\n";
+        }
+        $messageId1 = Cache::get("{$chatId}_GAME_ID_MESSAGE_ID", $messageId);
+        Cache::delete("{$chatId}_GAME_ID_MESSAGE_ID");
+        $data = $this->getGameInfo($room, $chatId, $room->member_id == $chatId, $text);
+        $data['chat_id'] = $chatId;
+        if ($messageId1) $data['message_id'] = $messageId1;
+        Cache::delete(get_step_key($chatId));
+        if ($notice) return [$data, $notice];
+        return [$data];
+    }
+
+
+    /** 确认创建房间
+     * @param $chatId
+     * @return array
+     */
+    public function done($chatId, $messageId)
+    {
+        $room = Room::where('status', 0)->where('member_id', $chatId)->first();
+        if (!$room) return [['chat_id' => $chatId, 'text' => '房间不存在,请先创建房间 /room', 'message_id' => $messageId]];
+        $text = "";
+        if ($room->room_id == '') $text = '请设置房间号';
+        if ($room->base_score == 0) $text = '请选择底分';
+        if ($room->participants == 0 || $room->rounds == 0) $text = '请设置人数/局数';
+        if ($text) return [['chat_id' => $chatId, 'text' => $text]];
+
+        $user = User::where('member_id', $chatId)->first();
+        $ru = new RoomUser();
+        $ru->room_id = $room->room_id;
+        $ru->member_id = $chatId;
+        $ru->status = 0;
+        $ru->first_name = $user->first_name;
+        $ru->save();
+        $room->join_count += 1;
+        $room->save();
+        $data = RoomService::getGameIdMessage($chatId, $room->game_name, $messageId);
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_GAME_ID);
+        Cache::delete("{$chatId}_BASE_SCORE");
+        return [$data];
+    }
+
+    //设置游戏介绍
+    public function setIntroduction($chatId, $introduction, $messageId)
+    {
+        $room = Room::where('status', 0)->where('member_id', $chatId)->first();
+        if (!$room) return ['chat_id' => $chatId, 'text' => '设置失败,没有正在创建的房间', 'reply_to_message_id' => $messageId];
+        $room->introduction = $introduction;
+        $room->save();
+
+        Cache::delete(get_step_key($chatId));
+        $messageId1 = Cache::get("{$chatId}_CREATE_ROOM_MESSAGE_ID");
+        $res = $this->getCreateButton($room, $chatId);
+        $res['message_id'] = $messageId1;
+        return $res;
+    }
+
+
+    /**
+     * 设置人数和局数
+     * @param $chatId string 用户的tg ID
+     * @param $array array 设置房间号的前缀【房间号】
+     * @throws TelegramSDKException
+     */
+    public function setNumberOfGames($chatId, $array, $messageId)
+    {
+        $room = Room::where('status', 0)->where('member_id', $chatId)->first();
+        if (!$room) return ['chat_id' => $chatId, 'text' => '设置失败,没有正在创建的房间', 'reply_to_message_id' => $messageId];
+        if (count($array) != 2) return ['chat_id' => $chatId, 'text' => '设置失败,请发送游戏人数', 'reply_to_message_id' => $messageId];
+        $array[0] = intval($array[0]);
+        $array[1] = intval($array[1]);
+
+        if (!is_int($array[1]) || $array[1] < 1) {
+            return ['chat_id' => $chatId, 'text' => '设置失败,请发送游戏局数', 'reply_to_message_id' => $messageId];
+        }
+        if (!is_int($array[0]) || $array[0] < 2) {
+            return ['chat_id' => $chatId, 'text' => '设置失败,请发送游戏人数', 'reply_to_message_id' => $messageId];
+        }
+        $room->participants = $array[0];
+        $room->rounds = $array[1];
+        $room->save();
+
+
+        // $text = "{$room->game_name}   {$room->participants}人   {$room->rounds}局\n";
+        // $text .= "房间号:{$room->room_id}\n";
+        // $text .= "底分:{$room->base_score} USDT\n";
+        // $text .= "中途加入:".($room->midway?'允许':'不允许')." \n";
+        // $text .= "-------------------\n";
+        // $text .= "请输入游戏介绍\n";
+        // $messageId1 = Cache::get("{$chatId}_CREATE_ROOM_MESSAGE_ID");
+        // Cache::put(get_step_key($chatId), StepStatus::INPUT_GAME_INTRODUCTION);
+        // Cache::put("{$chatId}_CREATE_ROOM_MESSAGE_ID", $messageId1);
+        // return [
+        //     'chat_id' => $chatId,
+        //     'text' => $text,
+        //     'message_id' => $messageId1,
+        //     'reply_markup' => json_encode(['inline_keyboard' => [[['text' => '❌取消', 'callback_data' => "games@@cancel"]]]])
+        // ];
+        Cache::delete(get_step_key($chatId));
+        $messageId1 = Cache::get("{$chatId}_CREATE_ROOM_MESSAGE_ID");
+        $res = $this->getCreateButton($room, $chatId);
+        $res['message_id'] = $messageId1;
+        return $res;
+    }
+
+
+    /**
+     * 设置房间号
+     * @param $chatId string 用户的tg ID
+     * @param $roomId string 房间号
+     * @throws TelegramSDKException
+     */
+    public function setNum($chatId, $roomId, $messageId)
+    {
+        if (!preg_match('/^\d+$/', $roomId)) return ['chat_id' => $chatId, 'text' => '房间号错误', 'reply_to_message_id' => $messageId];
+        $room = Room::where('status', 0)->where('member_id', $chatId)->first();
+        if (!$room) return ['chat_id' => $chatId, 'text' => "禁止设置,没有正在创建的房间"];
+        $isRoom = Room::where('room_id', $roomId)
+            ->where('room_id', '<>', $roomId)
+            ->first();
+        if ($isRoom) return ['chat_id' => $chatId, 'text' => '房间号已存在,请检查房间号', 'reply_to_message_id' => $messageId];
+        $room->room_id = $roomId;
+        $room->old_room_id = $roomId;
+        $room->save();
+
+
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_ROOM_NUM);
+        $text = "游戏:{$room->game_name}\n";
+        $text .= "底分:{$room->base_score} USDT\n";
+        $text .= "中途加入:".($room->midway?'允许':'不允许')." \n";
+        $text .= "房间号:$roomId\n";
+        $text .= "-------------------\n";
+        $text .= "请输入游戏人数和局数\n";
+        $text .= "例如:4人5局\n";
+        $text .= "那么请发送:4/5\n";
+        $messageId1 = Cache::get("{$chatId}_CREATE_ROOM_MESSAGE_ID");
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId1,
+            'reply_markup' => json_encode(['inline_keyboard' => [[['text' => '❌取消', 'callback_data' => "games@@cancel"]]]])
+        ];
+    }
+
+
+    public function setGameName($chatId, $gameName, $messageId)
+    {
+        $room = Room::where('status', 0)->where('member_id', $chatId)->first();
+        if (!$room) {
+            return ['chat_id' => $chatId, 'text' => "禁止设置,没有正在创建的房间", 'reply_to_message_id' => $messageId];
+        }
+        $room->game_name = $gameName;
+        $room->save();
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_MIDWAY);
+        $text = "游戏:{$room->game_name}\n";
+        $text .= "底分:{$room->base_score} USDT\n";
+        $text .= "-------------------\n";
+        $text .= "请选择是否允许中途加入游戏\n";
+        $messageId1 = Cache::get("{$chatId}_CREATE_ROOM_MESSAGE_ID");
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId1,
+            'reply_markup' => json_encode(['inline_keyboard' => [[
+                ['text' => '✅ 允许', 'callback_data' => "setGameMidway@@1"],
+                ['text' => '❌ 不允许', 'callback_data' => "setGameMidway@@0"]
+            ], [['text' => '❌取消', 'callback_data' => "games@@cancel"]]]])
+        ];
+    }
+
+    /**
+     * 设置游戏是否允许中途加入
+     * @param $chatId 用户TG ID
+     * @param $midway   中途加入 1允许 0 不允许
+     * @param $messageId 消息ID
+     * @throws TelegramSDKException
+     */
+    public function setGameMidway($chatId, $midway, $messageId)
+    {
+
+        $room = Room::where('status', 0)->where('member_id', $chatId)->first();
+        if (!$room) {
+            return ['chat_id' => $chatId, 'text' => "禁止设置,没有正在创建的房间", 'reply_to_message_id' => $messageId];
+        }
+        $room->midway = $midway;
+        $room->save();
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_ROOM_ID);
+        $text = "游戏:{$room->game_name}\n";
+        $text .= "底分:{$room->base_score} USDT\n";
+        $text .= "中途加入:".($room->midway?'允许':'不允许')." \n";
+        $text .= "-------------------\n";
+        $text .= "请发送三方游戏的房间号码";
+        $messageId1 = Cache::get("{$chatId}_CREATE_ROOM_MESSAGE_ID");
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId1,
+            'reply_markup' => json_encode(['inline_keyboard' => [[['text' => '❌取消', 'callback_data' => "games@@cancel"]]]])
+        ];
+    }
+
+
+    /**
+     * 选择房间低分
+     * @param $chatId
+     * @throws TelegramSDKException
+     */
+    public function setMin($data, $chatId, $messageId)
+    {
+        $roomMin = preg_replace('/^roomMin@@/', '', $data);
+        $roomMin = intval($roomMin);
+        $user = User::where('member_id', $chatId)->first();
+        $zuiDi = $roomMin * 100;
+
+        $wallet = Wallet::where('member_id', $chatId)->first();
+
+        if ($wallet->available_balance < $zuiDi) {
+            $text = "👤 {$user->first_name}({$user->member_id})\n";
+            $text .= "\n";
+            $text .= "💰钱包余额\n";
+            $temp = floatval($wallet->available_balance);
+            $text .= "USDT:{$temp}\n";
+            $text .= "\n";
+            $text .= "您创建的是{$roomMin}USDT的房间,最低余额{$zuiDi}USDT,请重新选择或充值余额后再创建房间!\n";
+            return ['chat_id' => $chatId, 'text' => $text];
+        } else {
+            $room = Room::where('status', 0)->where('member_id', $chatId)->first();
+            if (!$room) {
+                return ['chat_id' => $chatId, 'text' => "禁止设置,没有正在创建的房间"];
+            }
+            $room->base_score = $roomMin;
+            $room->save();
+            $text = "底分:{$roomMin} USDT\n";
+            $text .= "-------------------\n";
+            $text .= "请选择游戏";
+            $games = Game::all();
+            $keyboard = [];
+            foreach ($games as $index => $item) {
+                if ($index % 2 == 0) {
+                    $keyboard[] = [['text' => $item['game_name'], 'callback_data' => "inputGameName@@{$item['game_name']}"]];
+                } else {
+                    $keyboard[count($keyboard) - 1][] = ['text' => $item['game_name'], 'callback_data' => "inputGameName@@{$item['game_name']}"];
+                }
+            }
+
+            if (count($keyboard[count($keyboard) - 1]) > 1) {
+                $keyboard[] = [['text' => '❌取消', 'callback_data' => "games@@cancel"]];
+            } else {
+                $keyboard[count($keyboard) - 1][] = ['text' => '❌取消', 'callback_data' => "games@@cancel"];
+            }
+
+//            Cache::put(get_step_key($chatId), StepStatus::INPUT_GAME_NAME);
+            Cache::put("{$chatId}_CREATE_ROOM_MESSAGE_ID", $messageId);
+            return [
+                'chat_id' => $chatId,
+                'text' => $text,
+                'message_id' => $messageId,
+                'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+            ];
+        }
+    }
+
+
+    /**
+     * 创建房间
+     * @param $chatId
+     * @throws TelegramSDKException
+     */
+    public function create($chatId)
+    {
+        $room = Room::whereIn('status', [0, 1, 2, 4])
+            ->where('member_id', $chatId)->first();
+        $ru = RoomUser::where('member_id', $chatId)
+            ->whereIn('status', [0, 1, 2, 3])->first();
+        if (($room && $room->status !== 0) || $ru) {
+            return ['chat_id' => $chatId, 'text' => "您有未结算对局,禁止创建房间"];
+        }
+        if (!$room) {
+            $room = new Room();
+            $room->member_id = $chatId;
+            $room->game_name = '';
+            $room->save();
+        }
+        $keyboard = $this->getChooseBaseScoreMsg();
+        $keyboard[] = [['text' => '❌取消', 'callback_data' => "games@@cancel"]];
+        return [
+            'chat_id' => $chatId,
+            'text' => "👇请选择你房间底分",
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+    /**
+     * 重置房间号
+     * @param $roomId string 房间号
+     */
+    public static function resetRoomId($roomId)
+    {
+        $num = self::model()::where('old_room_id', $roomId)->count();
+        $room = Room::where('room_id', $roomId)->first();
+        $newRoomId = $roomId . '_' . $num;
+        if ($room) {
+            $room->room_id = $newRoomId;
+            $room->save();
+        }
+        RoomUser::where('room_id', $roomId)->update(['room_id' => $newRoomId]);
+        return $newRoomId;
+    }
+
+    /**
+     * @description: 结算通知
+     * @return {*}
+     */    
+   public static function noticeSettle()
+    {
+        try {
+            $time = Carbon::now()->subMinutes(20)->format('Y-m-d H:i:s');
+
+            $roomList = self::model()::where('status', 2)
+                ->where('settle_status', 0)
+                ->where('updated_at', '<', $time)
+                ->get();
+
+            $telegram = new Api(config('services.telegram.token'));
+
+            foreach ($roomList as $room) {
+                try {
+                    $list = RoomUser::where('room_id', $room->room_id)
+                        ->where('status', 2)
+                        ->get();
+
+                    if ($list->isEmpty()) continue;
+
+                    $text = "❗️房间号:{$room->room_id} \n";
+                    $text .= "‼️您有待结算的游戏,请及时进行结算。\n";
+
+                    foreach ($list as $item) {
+                        try {
+                            $keyboard = [[
+                                ['text' => '结算游戏', 'callback_data' => "games@@settle{$room->room_id}"]
+                            ]];
+
+                            $message = [
+                                'chat_id' => $item->member_id,
+                                'text' => $text,
+                                'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+                            ];
+
+                            $telegram->sendMessage($message);
+
+
+
+                        } catch (\Exception $e) {
+                            Log::error("发送消息失败:房间 {$room->room_id}, 用户 {$item->member_id}, 错误:" . $e->getMessage());
+                            continue; // 跳过当前用户
+                        }
+                    }
+                } catch (\Exception $e) {
+                    Log::error("处理房间 {$room->room_id} 失败:" . $e->getMessage());
+                    continue; // 跳过当前房间
+                }
+
+                $room->settle_status = 1; // 已通知
+                $room->save();
+            }
+
+        } catch (\Exception $e) {
+            Log::error("noticeSettle 整体执行失败:" . $e->getMessage());
+        }
+    }
+
+}

+ 252 - 0
app/Services/SettlementService.php

@@ -0,0 +1,252 @@
+<?php
+
+
+namespace App\Services;
+
+
+use App\Constants\StepStatus;
+use App\Exceptions\MessageException;
+use App\Models\BalanceLog;
+use App\Models\Config;
+use App\Models\Room;
+use App\Models\RoomUser;
+use App\Models\User;
+use App\Models\Wallet;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Storage;
+use Telegram\Bot\Api;
+
+//结算相关
+class SettlementService
+{
+    private $telegram;
+
+    public function __construct()
+    {
+        $this->telegram = new Api(config('services.telegram.token'));
+    }
+
+
+    //用户提交结算数据
+    public function submitSettle($chatId, $score, $messageId = null)
+    {
+        if (!preg_match('/^-?\d+$/', $score)) {
+            return [
+                'status' => false,
+                'data' => [[
+                    'chat_id' => $chatId,
+                    'text' => '输入错误,请发送结算分数',
+                    'reply_to_message_id' => $messageId
+                ]]
+            ];
+        }
+        $score = intval($score);
+        $ru = RoomUser::whereIn('status', [2, 3])
+            ->where('member_id', $chatId)->first();
+        if (!$ru) {
+            return [
+                'status' => false,
+                'data' => [['chat_id' => $chatId, 'text' => '没有待结算的对局']]
+            ];
+        }
+        $ru->status = 3;
+        $ru->score = $score;
+        $ru->save();
+
+        $list = RoomUser::where('status', 3)
+            ->where('room_id', $ru->room_id)
+            ->orderBy('score', 'desc')->get();
+        $count = RoomUser::where('room_id', $ru->room_id)->count();
+
+
+        if (count($list) == $count) {
+            $totalAmount = RoomUser::where('status', 3)
+                ->where('room_id', $ru->room_id)->sum('score');
+            if ($totalAmount != 0) {
+                $text = "❌经过系统结算,盈利额与亏损\n";
+                $text .= "额度无法匹配,无法进行结算,\n";
+                $text .= "请检查战绩是否正确,如果错误\n";
+                $text .= "请重新输入正确的战绩!\n\n";
+                $text .= "您输入的战绩如下:\n";
+                $arr = [];
+                foreach ($list as $item) {
+                    $str = $text . "--------------------------------\n";
+                    $str .= "房间号:{$item->room_id}\n";
+                    $str .= "用户:{$item->game_id}\n";
+                    $str .= "战绩:{$item->score}\n";
+                    $arr[] = [
+                        'chat_id' => $item->member_id,
+                        'text' => $str
+                    ];
+                }
+                return ['status' => false, 'data' => $arr];
+            }
+
+
+            //抽佣比例
+            $brokerage = Config::where('field', 'brokerage')->first();
+            $brokerage = $brokerage->val;
+
+            $room = Room::where('room_id', $ru->room_id)->first();
+            $room->status = 3;
+            $room->save();
+            $maxScore = 0;
+
+            foreach ($list as $index => $item) {
+                $a = bcmul($item->score, $room->base_score, 0);
+                if ($index == 0) $maxScore = $item->score;
+                //最赢着
+                if ($item->score == $maxScore) {
+                    $item->brokerage = bcmul($a, $brokerage, 10);
+                    $item->real_score = bcsub($a, $item->brokerage, 10);
+                }//
+                //其余人员
+                else {
+                    $item->brokerage = 0;
+                    $item->real_score = $a;
+                }
+                $item->status = 4;
+                $item->save();
+
+
+                $wallet = Wallet::where('member_id', $item->member_id)->first();
+                $available_balance = $wallet->available_balance;
+
+
+//                $user = User::where('member_id', $item->member_id)->first();
+                $usdt = bcadd($available_balance, $item->real_score, 10);
+                if ($usdt < 0) {
+                    $ttt = floatval($available_balance);
+                    $rr = "❌结算失败\n";
+                    $rr .= "您的余额不足\n\n";
+                    $rr .= "钱包余额:{$ttt} USDT\n";
+                    $ttt = floatval($item->real_score);
+                    $rr .= "结算金额:{$ttt} USDT\n";
+                    $rr .= "请充值后重新进行结算";
+                    $keyboard = [[
+                        ['text' => '➕充值', 'callback_data' => "topup@@topup"],
+                    ]];
+                    $errorMsg = json_encode([
+                        'chat_id' => $item->member_id,
+                        'text' => $rr,
+                        'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+                    ]);
+                    throw new MessageException($errorMsg, 400);
+                }
+                $wallet->available_balance = $usdt;
+                $wallet->save();
+
+                $newRoomId = RoomService::resetRoomId($item->room_id);
+
+                BalanceLogService::addLog(
+                    $item->member_id,
+                    $item->real_score,
+                    $available_balance,
+                    $wallet->available_balance,
+                    '结算',
+                    $item->id,
+                    '',
+                    $newRoomId
+                );
+
+
+            }
+            $arr = [];
+            foreach ($list as $item) {
+                $text = "✅ 所有用户已经上传并且输入战绩结果,系统计算完毕!如果有纠纷,余额已变动,请及时联系在线客服\n";
+                $text .= "🗂 {$room->game_name}   {$room->participants}人   {$room->rounds}局\n";
+                $text .= "房间号:{$room->room_id}\n";
+                $text .= "底分:{$room->base_score} USDT\n";
+                $text .= "-------------------------------------\n";
+                foreach ($list as $item1) {
+                    $text .= "用户:{$item1->first_name}({$item1->game_id})\n";
+                    $text .= "战绩:{$item1->score}\n";
+                    $temp = floatval($item1->real_score);
+                    $text .= "盈亏:{$temp} USDT\n";
+                    $temp = floatval($item1->brokerage);
+                    if ($temp > 0) $text .= "抽佣:{$temp} USDT\n";
+                    $text .= "-------------------------------------\n";
+                }
+                $arr[] = [
+                    'chat_id' => $item->member_id,
+                    'text' => $text
+                ];
+            }
+
+
+            return ['status' => true, 'data' => $arr];
+
+        }
+        $keyboard = [[
+            ['text' => '返回', 'callback_data' => "games@@home{$ru->room_id}"],
+        ]];
+        $score = $score >= 0 ? "+{$score}" : $score;
+        Cache::delete(get_step_key($chatId));
+        return ['status' => false, 'data' => [[
+            'chat_id' => $chatId,
+            'text' => "🏆用户(ID:{$chatId})已上传战绩,并输入结果:\n{$score}。请等待其余用户完成上传,系统将在所有\n人输入后进行结算。",
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ]]];
+
+    }
+
+    //用户点击结算游戏
+    public function settle($roomId, $chatId, $messageId)
+    {
+        $ru = RoomUser::where('room_id', $roomId)
+            ->whereIn('status', [2, 3])
+            ->where('member_id', $chatId)->first();
+        if (!$ru) return ['chat_id' => $chatId, 'text' => '❌游戏未开始或已结束', 'message_id' => $messageId];
+        $keyboard = [[
+            ['text' => '返回', 'callback_data' => "games@@home{$roomId}"],
+        ]];
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_IMAGE);
+        return [
+            'chat_id' => $chatId,
+            'text' => "‼️ 请务必上传战绩截图!如出现纠纷,系统将以截图\n作为处理依据。若未上传战绩截图,产生的纠纷责任\n将由您自行承担。",
+//            'message_id' => $messageId,
+//            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+
+    //用户发送结算截图
+    public function photo($photo, $chatId)
+    {
+        $file = $this->telegram->getFile(['file_id' => $photo['file_id']]);
+        $filePath = $file->getFilePath();
+        $token = config('services.telegram.token');
+        $file_url = "https://api.telegram.org/file/bot{$token}/{$filePath}";
+        $file_info = pathinfo($file_url);
+        $roomUser = RoomUser::whereIn('status', [2, 3])
+            ->where('member_id', $chatId)->first();
+        if ($roomUser) {
+            $roomId = $roomUser->room_id;
+            $save_path = storage_path("app/public/images/{$roomId}/");
+            if (!is_dir($save_path)) {
+                mkdir($save_path, 0777, true);
+            }
+            $fileName = "{$chatId}.{$file_info['extension']}";
+            $save_path .= $fileName;
+            file_put_contents($save_path, file_get_contents($file_url));
+            $path = Storage::url("images/{$roomId}/" . $fileName);
+            $roomUser->screenshot = $path;
+            $roomUser->save();
+
+            $text = "✅ 已收到您的战绩截图。请根据结果输入战绩分数:\n";
+            $text .= "如果盈利 10,输入 10\n";
+            $text .= "如果亏损 10,输入 -10\n";
+            $text .= "系统将在所有人输入战绩后进行结算。";
+            Cache::put(get_step_key($chatId), StepStatus::INPUT_SCORE);
+            $keyboard = [[
+                ['text' => '返回', 'callback_data' => "games@@home{$roomId}"],
+            ]];
+            return [
+                'chat_id' => $chatId,
+                'text' => $text,
+//                'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+            ];
+        }
+        return false;
+    }
+}

+ 261 - 0
app/Services/TopUpService.php

@@ -0,0 +1,261 @@
+<?php
+
+namespace App\Services;
+
+
+//充值服务
+use App\Constants\StepStatus;
+use App\Models\BalanceLog;
+use App\Models\Config;
+use App\Models\Recharge;
+use App\Models\Wallet;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Storage;
+use Telegram\Bot\Api;
+use Telegram\Bot\FileUpload\InputFile;
+use Telegram\Bot\Exceptions\TelegramSDKException;
+
+class TopUpService
+{
+
+    /**
+     * 到账通知
+     * @param $memberId string 要通知的TgID
+     * @param $text string 通知内容
+     * @return bool 是否发送成功
+     */
+    public static function notifyTransferSuccess($memberId, $text)
+    {
+        try {
+            $telegram = new Api(config('services.telegram.token'));
+            $telegram->sendMessage(['chat_id' => $memberId, 'text' => $text]);
+        } catch (TelegramSDKException $e) {
+            return false;
+        }
+        return true;
+    }
+
+    public function bill($chatId, $firstName, $messageId = null, $page = 1, $limit = 5)
+    {
+        RechargeService::syncUsdtRechargeRecords($chatId);
+        $list = BalanceLog::where('member_id', $chatId)
+//            ->where('change_type', '充值')
+            ->orderBy('created_at', 'desc')
+            ->forPage($page, $limit)
+            ->get();
+
+        $count = BalanceLog::where('member_id', $chatId)
+//            ->where('change_type', '充值')
+            ->count();
+
+        $text = "👤 {$firstName}({$chatId})    钱包充值记录\n\n";
+        foreach ($list as $item) {
+
+
+            $amount = floatval($item->amount);
+            $amount = $amount < 0 ? ("➖ " . ($amount * -1)) : "➕ $amount";
+            $balance = floatval($item->after_balance);
+            $text .= "-------------------------------------\n";
+            $text .= "{$amount} USDT\n余额:{$balance} \n";
+            $text .= "类别:{$item->change_type}\n";
+            if ($item->remark) {
+                $text .= "说明:{$item->remark}\n";
+            }
+            $text .= "日期:{$item->created_at}\n";
+        }
+
+        if ($page > 1) {
+            $keyboard[] = [
+                ['text' => "👆上一页", 'callback_data' => "topUpBillNextPage@@" . ($page - 1)]
+            ];
+        }
+        $allPage = ceil($count / $limit);
+        if ($allPage > $page) {
+            if ($page > 1) {
+                $keyboard[count($keyboard) - 1][] = ['text' => "👇下一页", 'callback_data' => "topUpBillNextPage@@" . ($page + 1)];
+            } else {
+                $keyboard[] = [
+                    ['text' => "👇下一页", 'callback_data' => "topUpBillNextPage@@" . ($page + 1)]
+                ];
+            }
+        }
+        $keyboard[] = [
+            ['text' => "返回", 'callback_data' => "topUp@@home"]
+        ];
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+    /**
+     * 充值图片
+     * @param $chatId
+     * @param $photo
+     * @throws TelegramSDKException
+     */
+    public static function photo($chatId, $photo)
+    {
+        $telegram = $telegram = new Api(config('services.telegram.token'));
+        $file = $telegram->getFile(['file_id' => $photo['file_id']]);
+        $filePath = $file->getFilePath();
+        $token = config('services.telegram.token');
+        $file_url = "https://api.telegram.org/file/bot{$token}/{$filePath}";
+        $file_info = pathinfo($file_url);
+        $save_path = storage_path("app/public/images/{$chatId}/");
+        if (!is_dir($save_path)) {
+            mkdir($save_path, 0777, true);
+        }
+        $fileName = uniqid();
+        $fileName = "{$fileName}.{$file_info['extension']}";
+        $save_path .= $fileName;
+        file_put_contents($save_path, file_get_contents($file_url));
+        $path = Storage::url("images/{$chatId}/" . $fileName);
+        $amount = Cache::get("{$chatId}_TOP_UP_AMOUNT");
+        $toAddress = Cache::get("{$chatId}_TOP_UP_ADDRESS");
+        $recharge = new Recharge();
+        $recharge->member_id = $chatId;
+        $recharge->net = "TRC20";
+        $recharge->coin = "USDT";
+        $recharge->amount = $amount;
+        $recharge->to_address = $toAddress;
+        $recharge->status = 0;
+        $recharge->type = 2;
+        $recharge->image = $path;
+        $recharge->save();
+        Cache::delete(get_step_key($chatId));
+        Cache::delete("{$chatId}_TOP_UP_AMOUNT");
+        Cache::delete("{$chatId}_TOP_UP_ADDRESS");
+        return [
+            'chat_id' => $chatId,
+            'text' => '已提交充值凭证,请等待系统审核完成'
+        ];
+    }
+
+    //输入充值金额
+    public static function inputAmount($chatId, $amount, $messageId)
+    {
+        if (!preg_match('/^\d+(\.\d+)?$/', $amount)) {
+            return [
+                'chat_id' => $chatId,
+                'text' => "金额输入不正确,请发送充值数字",
+                'reply_to_message_id' => $messageId
+            ];
+        }
+        Cache::put("{$chatId}_TOP_UP_AMOUNT", $amount);
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_TOP_UP_IMAGE);
+        return [
+            'chat_id' => $chatId,
+            'text' => "请发送您的充值凭证截图,方便系统验证",
+        ];
+    }
+
+    //用户点击我已付款 (手动)
+    public static function pay2($chatId)
+    {
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_TOP_UP_MONEY);
+        return [
+            'chat_id' => $chatId,
+            'text' => '请输入充值金额'
+        ];
+    }
+
+    //用户点击我已付款
+    public function done($chatId)
+    {
+        RechargeService::syncUsdtRechargeRecords($chatId);
+        $keyboard = [
+            [['text' => "🔙返回", 'callback_data' => "topUp@@home"]]
+        ];
+        return [
+            'chat_id' => $chatId,
+            'text' => '请耐心等待,充值成功后 Bot 会通知您!',
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard]),
+        ];
+    }
+
+    //获取充值二维码
+    public function scan($chatId, $messageId)
+    {
+
+        $receivingType = Config::where('field', 'receiving_type')->first()->val;
+        //自动
+        if ($receivingType == 1) {
+            $res = WalletService::getRechargeImageAddress($chatId);
+            $address = $res['address'];
+            $qrCode = $res['full_path'];
+        } //
+        //手动
+        else {
+            $address = Config::where('field', 'receiving_address')->first()->val;
+            $res = WalletService::getPlatformImageAddress($address);
+            $qrCode = $res['full_path'];
+            Cache::put("{$chatId}_TOP_UP_ADDRESS", $address);
+        }
+
+
+        $caption = "\n";
+        $caption .= "支持货币({$res['net']}):{$res['coin']}\n";
+        $caption .= "专属收款地址:\n";
+        $caption .= "{$address}\n";
+        $caption .= "\n";
+        $caption .= "⚠️提示:\n";
+        $caption .= "- 如果上面地址和二维码不一致,请不要付款!\n";
+        $caption .= "- 对上述地址充值后, 请点击我已付款!\n";
+        $caption .= "- 请耐心等待, 充值成功后 Bot 会通知您!\n";
+
+        $keyboard = [
+            [
+                ['text' => '📋 复制地址', 'copy_text' => ['text' => $address]],
+                ['text' => '✅ 我已付款', 'callback_data' => 'topUp@@pay']
+            ],
+            [
+                ['text' => "🔙返回", 'callback_data' => "topUp@@home1"],
+            ]
+        ];
+        //手动
+        if ($receivingType != 1) {
+            $keyboard[0][1] = ['text' => '✅ 我已付款', 'callback_data' => 'topUp@@pay2'];
+        }
+
+
+        return [
+            'chat_id' => $chatId,
+            'photo' => InputFile::create($qrCode),
+            'caption' => $caption,
+            'protect_content' => true,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard]),
+            'message_id' => $messageId
+        ];
+
+
+    }
+
+    public function index($chatId, $firstName, $messageId = null)
+    {
+        $wallet = Wallet::where('member_id', $chatId)->first();
+        $text = "👤 {$firstName}($chatId)\n";
+        $text .= "💰钱包余额\n";
+        $temp = floatval($wallet->available_balance);
+        $text .= "USDT:{$temp}\n";
+        $serviceAccount = Config::where('field', 'service_account')->first()->val;
+        $keyboard = [
+            [
+                ['text' => '➕充值', 'callback_data' => "topup@@topup"],
+                ['text' => '🧾账单', 'callback_data' => "topup@@bill"],
+            ],
+            [
+                ['text' => '👩 客服帮助', 'url' => "https://t.me/{$serviceAccount}"],
+                ['text' => '❌取消', 'callback_data' => "message@@close"],
+            ]
+        ];
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard]),
+            'message_id' => $messageId
+        ];
+    }
+}

+ 129 - 0
app/Services/TutorialService.php

@@ -0,0 +1,129 @@
+<?php
+
+
+namespace App\Services;
+
+
+class TutorialService
+{
+
+    public static function help($chatId, $index, $messageId)
+    {
+        $titles = ["💰️充值教程", "💸提现教程", "🏡创建房间教程", "📊如何结算?"];
+        $contents = [];
+        $contents[] = <<<EOF
+如何充值(购买USDT)
+
+1.1 购买数字货币
+在充值之前,您需要通过法币购买 USDT。建议使用信誉良好的中心化交易所,因为他们操作见简便切安全性高。以下是通用步骤:
+
+a.选择并注册交易所账号
+  - 选择您信任的中心化交易所(如 币安)
+  - 访问交易所官网并完成注册。
+  - 开启双重验证(如 Google 验证器)以提升账号安全性。
+  - 绑定邮箱和其他安全信息。
+  
+b.购买 USDT
+  - 如果您尚未拥有任何数字货币,请参考币安的官方教程进行购买。
+  - 购买完成后,您可以将 USDT 提现至我们的钱包。
+  
+1.2 充值到我们的钱包
+
+完成 USDT 购买后,按照以下步骤将数字资产转入我们:
+
+ a.获取充值地址
+   - Bot 中点击【+充值】,获取您的唯一充值地址。
+   - 复制该地址到剪贴板备用。
+   
+ b.在交易所提币
+   - 参考币安的提现操作教程进行提现。
+   - 提现时选择 Tron (TRC20) 网络。
+   - 注意交易所可能收取一定的服务费,具体以交易所显示为准。
+   
+ c.完成转账
+   - 等待区块链网络确认,将受到到账通知,USDT 将准入您的钱包。
+   - 提现成功后,您即可正常使用所有功能。
+EOF;
+
+        $userName = config("services.telegram.username");
+        $contents[] = <<<EOF
+将 USDT 转换为法币前,需先将您钱包中的 USDT 提现加到交易所,再在交易所出售数字资产兑换成法币。
+具体操作如下:
+1 将钱包中的 USDT 提现至交易所
+
+ a.获取交易所充值地址
+   - 参考交易所的官方指南获取您的充值地址。
+   
+ b.在频道提现
+   - 打开官方 Bot ,点击【-提币】。
+   - 选择【USDT(TRC20)】
+   - 输入提现金额,(注意 @{$userName} 将收取提币服务费)。
+   - 粘贴交易所提供的充值地址,或扫描充值地址二维码。
+   - 核对提现金额和手续费,提交提币申请。
+   
+ c.完成转账
+   - 等待区块链网络确认,USDT 将转入交易所账户。
+   
+2 在交易所出售 USDT 兑换法币
+ 
+ a.出售 USDT
+   - 登录交易,参考交易所的出售教程进行出售
+   
+ b.兑换法币
+   - 根据交易所的提示,完成出售 USDT ,收到买家的法币转账确认放行数字资产即可完成。
+EOF;
+        $contents[] = <<<EOF
+1.启动机器人后,点击 “功能菜单”。
+2.选择 “创建房间”。
+3.系统会返回可厕的房间类型(底分).
+4.点击你想创建的房间类型。
+5.系统会检测您的余额是否足够:
+  - 如果余额充足,会自动创建房间,并返回游戏信息。
+  - 如果余额不足,系统会提示您充值,并展示您专属的 USDT 地址和二维码。
+6.转账成功后系统会自动识别到账,并为您完成上分。
+7.进入频道后,即可邀请好友一起游戏。
+
+注意事项:
+  - 每个用户拥有专属充值地址,请勿转错。
+  - 充值时请仔细核对地址或二维码是否一致。
+  - 若超过30分钟未到账,将自动取消等待状态,请重新创建房间。
+EOF;
+        $contents[] = <<<EOF
+每局游戏结束后根据分数自动结算,系统将直接更新你的余额。
+EOF;
+
+        $text = "$titles[$index]\n\n";
+        $text .= $contents[$index];
+        $keyboard = [[
+            ['text' => "🔙返回", 'callback_data' => "Tutorial@@home"]
+        ]];
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+//            'parse_mode' => 'MarkdownV2',
+            'message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+
+    public function index($chatId)
+    {
+        $keyboard = [
+            [
+                ['text' => "💰️充值教程", 'callback_data' => "Tutorial@@0"],
+                ['text' => "💸提现教程", 'callback_data' => "Tutorial@@1"]
+            ],
+            [
+                ['text' => "🏡创建房间教程", 'callback_data' => "Tutorial@@2"],
+                ['text' => "📊如何结算?", 'callback_data' => "Tutorial@@3"]
+            ]
+        ];
+        return [
+            'chat_id' => $chatId,
+            'text' => "📖 请选择你想了解的帮助内容:\n",
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+
+    }
+}

+ 108 - 0
app/Services/UserService.php

@@ -0,0 +1,108 @@
+<?php
+
+
+namespace App\Services;
+
+
+use App\Services\BaseService;
+use App\Models\User;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Cache;
+
+class UserService extends BaseService
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */
+    public static function model(): string
+    {
+        return User::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+        if (isset($search['id']) && !empty($search['id'])) {
+            $where[] = ['id', '=', $search['id']];
+        }
+        if (isset($search['game_id']) && !empty($search['game_id'])) {
+            $where[] = ['game_id', '=', $search['game_id']];
+        }
+
+
+        if (isset($search['member_id']) && !empty($search['member_id'])) {
+            $where[] = ['member_id', '=', $search['member_id']];
+        }
+        if (isset($search['first_name']) && !empty($search['first_name'])) {
+            $where[] = ['first_name', '=', $search['first_name']];
+        }
+        if (isset($search['like_first_name']) && !empty($search['like_first_name'])) {
+            $where[] = ['first_name', 'like', "%" . $search['like_first_name'] . "%"];
+        }
+        return $where;
+    }
+
+    /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\User|null
+     */
+    public static function findOne(array $search): ?User
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 15;
+        $paginator = self::model()::where(self::getWhere($search))->with('wallet:user_id,member_id,address,available_balance')->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+    //设置游戏ID
+    public function setGameID($chatId, $gameId)
+    {
+        $gameId = trim($gameId);
+        if(!$gameId)return ['chat_id' => $chatId, 'text' => '❌游戏ID设置失败,请输入正确的游戏ID'];
+
+        if (User::where('game_id', $gameId)->where('member_id', '<>', $chatId)->first()) {
+            return ['chat_id' => $chatId, 'text' => '❌游戏ID设置失败,游戏ID已绑定其他用户'];
+        }
+        $user = User::where('member_id', $chatId)->first();
+        $user->game_id = $gameId;
+        $user->save();
+        return ['chat_id' => $chatId, 'text' => "✅ 游戏ID设置成功\n游戏ID:{$gameId}"];
+    }
+
+}

+ 231 - 0
app/Services/WalletService.php

@@ -0,0 +1,231 @@
+<?php
+
+
+namespace App\Services;
+
+use App\Services\BaseService;
+use App\Models\Wallet;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Cache;
+use App\Services\CoinService;
+use App\Services\UserService;
+use App\Helpers\TronHelper;
+use Illuminate\Support\Facades\Log;
+use App\Services\BalanceLogService;
+
+/**
+ * 用户虚拟币钱包
+*/
+class WalletService extends BaseService 
+{
+    /**
+     * @description: 模型
+     * @return {string}
+     */    
+    public static function model() :string
+    {
+        return Wallet::class;
+    }
+
+    /**
+     * @description: 枚举
+     * @return {*}
+     */    
+    public static function enum() :string
+    {
+        return '';
+    }
+
+    /**
+     * @description: 获取查询条件
+     * @param {array} $search 查询内容
+     * @return {array}
+     */    
+    public static function getWhere(array $search = []) :array
+    {
+        $where = [];
+        if(isset($search['coin']) && !empty($search['coin'])){
+            $where[] = ['coin', '=', $search['coin']];
+        }
+        if(isset($search['net']) && !empty($search['net'])){
+            $where[] = ['net', '=', $search['net']];
+        }
+        if(isset($search['address']) && !empty($search['address'])){
+            $where[] = ['address', '=', $search['address']];
+        }
+        if(isset($search['id']) && !empty($search['id'])){
+            $where[] = ['id', '=', $search['id']];
+        }
+        if(isset($search['member_id']) && !empty($search['member_id'])){
+            $where[] = ['member_id', '=', $search['member_id']];
+        }
+        return $where;
+    }
+
+     /**
+     * @description: 查询单条数据
+     * @param array $search
+     * @return \App\Models\Coin|null
+     */
+    public static function findOne(array $search): ?Wallet
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    /**
+     * @description: 查询所有数据
+     * @param array $search
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    /**
+     * @description: 分页查询
+     * @param array $search
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit'])?$search['limit']:15;
+        $paginator = self::model()::where(self::getWhere($search))->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+    /**
+     * @description: 为用户创建虚拟钱包
+     * @param {int} $memberId 用户ID
+     * @return {*}
+     */    
+    public static function createVirtualWallets(int $memberId)
+    {
+        $coins = CoinService::findAll(['coin' => 'USDT']);
+
+        $users = UserService::findOne(['member_id' => $memberId]);
+
+
+        $walletsData = $coins->map(function($coin) use ($memberId,$users) {
+
+            switch($coin->coin){
+                case 'USDT':
+                    $trons = TronHelper::createAddress($memberId);
+                    break;
+                default:
+                    $trons = [];
+            }
+
+            return [
+                'user_id' => $users['id'],
+                'member_id' => $memberId,
+                'coin' => $coin->coin,
+                'net' => $coin->net,
+                'address' => $trons['address']??'',
+                'private_key' => $trons['private_key']??'',
+                'available_balance' => 0,
+                'frozen_balance' => 0
+            ];
+
+        })->toArray();
+        
+        // 批量创建钱包以提高性能
+        self::model()::insert($walletsData);
+
+        return self::findAll(['member_id' => $memberId]);
+                
+    }
+
+    /**
+     * @description: 获取用户的钱包
+     * @param {int} $memberId 
+     * @return {*}
+     */    
+    public static function getUserWallet(int $memberId)
+    {
+        $wallets = self::findAll(['member_id' => $memberId]);
+
+        if($wallets->isEmpty()){
+            $wallets = self::createVirtualWallets($memberId);
+        }
+
+        $wallets->map(function($wallet) {
+                    $wallet->available_balance = removeZero($wallet->available_balance);
+                    $wallet->frozen_balance = removeZero($wallet->frozen_balance);
+                    // $wallet->total_balance = $wallet->available_balance + $wallet->frozen_balance;
+                    return $wallet;
+                });
+        return $wallets;
+    }
+
+    /**
+     * @description: 获取用户充值钱包地址
+     * @param {*} $memberId
+     * @return {*}
+     */    
+    public static function getRechargeImageAddress($memberId)
+    {
+        self::getUserWallet($memberId);
+
+        $info = self::findOne(['member_id' => $memberId]);
+        $path = self::rechargeQrCodeExists($info->address);
+        if(empty($path)){
+            $path = self::createRechargeQrCode($info->address);
+        }
+
+        // $host = config('app.url'); // 通常在 .env 中配置 APP_URL
+        return [
+            'coin' => $info->coin,
+            'net' => $info->net,
+            'address' => $info->address,
+            'path' => $path,
+            'full_path' => asset($path)
+        ];
+    }
+
+    /**
+     * @description: 获取平台充值钱包地址
+     * @param {*} $address
+     * @return {*}
+     */    
+    public static function getPlatformImageAddress($address)
+    {
+         $path = self::rechargeQrCodeExists($address);
+        if(empty($path)){
+            $path = self::createRechargeQrCode($address);
+        }
+
+        // $host = config('app.url'); // 通常在 .env 中配置 APP_URL
+        return [
+            'coin' => 'USDT',
+            'net' => 'TRC20',
+            'address' => $address,
+            'path' => $path,
+            'full_path' => asset($path)
+        ];
+    }
+
+    /**
+     * @description: 更新余额
+     * @param {*} $memberId
+     * @param {*} $amount
+     * @return {*}
+     */    
+    public static function updateBalance($memberId , $amount)
+    {
+        $data = [];
+        $self = self::findOne(['member_id' => $memberId]);
+        $data['before_balance'] = $self->available_balance; // 操作前余额
+        $self->available_balance += $amount;
+        $data['after_balance'] = $self->available_balance;
+        $self->save();
+        return $data;
+    }
+    
+    public static function getBalance($memberId)
+    {
+
+    }
+    
+}

+ 517 - 0
app/Services/WithdrawService.php

@@ -0,0 +1,517 @@
+<?php
+
+
+namespace App\Services;
+
+
+use App\Constants\StepStatus;
+use App\Models\Address;
+use App\Models\BalanceLog;
+use App\Models\Config;
+use App\Models\RoomUser;
+use App\Models\User;
+use App\Models\Wallet;
+use App\Models\Withdraw;
+use Illuminate\Support\Facades\Cache;
+use LaravelLang\Publisher\Console\Add;
+use Telegram\Bot\Api;
+use Telegram\Bot\Exceptions\TelegramSDKException;
+
+class WithdrawService
+{
+    private $serviceCharge;
+
+
+    public static function model(): string
+    {
+        return Withdraw::class;
+    }
+
+    public static function enum(): string
+    {
+        return '';
+    }
+
+    public static function getWhere(array $search = []): array
+    {
+        $where = [];
+        if (isset($search['id']) && !empty($search['id'])) {
+            $where[] = ['id', '=', $search['id']];
+        }
+
+
+        if (isset($search['member_id']) && !empty($search['member_id'])) {
+            $where[] = ['member_id', '=', $search['member_id']];
+        }
+        if (isset($search['status']) && $search['status'] != '') {
+            $where[] = ['status', '=', $search['status']];
+        }
+        return $where;
+    }
+
+    public static function findOne(array $search): ?Withdraw
+    {
+        return self::model()::where(self::getWhere($search))->first();
+    }
+
+    public static function findAll(array $search = [])
+    {
+        return self::model()::where(self::getWhere($search))->get();
+    }
+
+    public static function paginate(array $search = [])
+    {
+        $limit = isset($search['limit']) ? $search['limit'] : 10;
+        $paginator = self::model()::where(self::getWhere($search))
+            ->orderBy('created_at', 'desc')->paginate($limit);
+        return ['total' => $paginator->total(), 'data' => $paginator->items()];
+    }
+
+
+    public function __construct()
+    {
+        $serviceCharge = Config::where('field', 'service_charge')->first()->val;
+        $serviceCharge = floatval($serviceCharge);
+        $this->serviceCharge = $serviceCharge;
+    }
+
+    public static function notify($data)
+    {
+        try {
+            $telegram = new Api(config('services.telegram.token'));
+            $telegram->sendMessage($data);
+        } catch (TelegramSDKException $e) {
+            return false;
+        }
+        return true;
+    }
+
+
+    //提现账单
+    public function bill($chatId, $firstName, $messageId = null, $page = 1, $limit = 5)
+    {
+        $list = Withdraw::where('member_id', $chatId)
+            ->orderBy('id', 'desc')
+            ->forPage($page, $limit)
+            ->get();
+
+
+        $count = Withdraw::where('member_id', $chatId)
+            ->whereIn('status', [0, 1])
+            ->count();
+
+        $status = ['等待放行', '已到账', "失败"];
+        $text = "👤 {$firstName}({$chatId})    钱包提现记录\n\n";
+
+        foreach ($list as $item) {
+            $amount = floatval($item->amount);
+            $balance = floatval($item->after_balance);
+            $text .= "-------------------------------------\n";
+            $text .= "➖ {$amount} USDT\n余额:{$balance} \n";
+            $text .= "类别:提现\n";
+            $text .= "状态:{$status[$item->status]}\n";
+            if ($item->remark) {
+                $text .= "说明:{$item->remark}\n";
+            }
+            $text .= "日期:{$item->created_at}\n";
+        }
+
+        if ($page > 1) {
+            $keyboard[] = [
+                ['text' => "👆上一页", 'callback_data' => "withdrawBillNextPage@@" . ($page - 1)]
+            ];
+        }
+        $allPage = ceil($count / $limit);
+        if ($allPage > $page) {
+            if ($page > 1) {
+                $keyboard[count($keyboard) - 1][] = ['text' => "👇下一页", 'callback_data' => "withdrawBillNextPage@@" . ($page + 1)];
+            } else {
+                $keyboard[] = [
+                    ['text' => "👇下一页", 'callback_data' => "withdrawBillNextPage@@" . ($page + 1)]
+                ];
+            }
+        }
+
+        $keyboard[] = [
+            ['text' => "🔙返回", 'callback_data' => "withdraw@@home"]
+        ];
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+
+    //删除地址
+    public static function delAddress($chatId, $id, $messageId)
+    {
+        Address::where('id', $id)
+            ->where('member_id', $chatId)->delete();
+        return WithdrawService::getAddress($chatId, $messageId);
+    }
+
+    //地址详情
+    public static function addressDetails($chatId, $messageId, $id)
+    {
+        $text = "*TRC20 地址管理*\n\n";
+        $address = Address::where('id', $id)
+            ->where('member_id', $chatId)->first();
+        $text .= "地址:{$address->address}\n";
+        $text .= "别名:{$address->alias}";
+
+        $keyboard = [
+            [['text' => '❌删除该地址', 'callback_data' => "withdrawAddress@@del{$id}"]],
+            [['text' => '↩️返回列表', 'callback_data' => 'withdraw@@address']]
+        ];
+        return [
+            'chat_id' => $chatId,
+            'parse_mode' => 'MarkdownV2',
+            'text' => $text,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard]),
+            'message_id' => $messageId
+        ];
+    }
+
+
+    //输入别名
+    public static function inputAlias($chatId, $alias, $messageId)
+    {
+        if (mb_strlen($alias) > 20) {
+            return [
+                'chat_id' => $chatId,
+                'text' => "别名不能超过20个字,请重新输入",
+                'reply_to_message_id' => $messageId,
+            ];
+        }
+        $address = Cache::get("{$chatId}_address");
+        $a = new Address();
+        $a->address = $address;
+        $a->alias = $alias;
+        $a->member_id = $chatId;
+        $a->save();
+        Cache::delete("{$chatId}_address");
+        Cache::delete(get_step_key($chatId));
+        return self::getAddress($chatId, $messageId);
+    }
+
+    //用户输入TRC20地址
+    public static function inputAddress($chatId, $address, $messageId)
+    {
+        if (!preg_match('/^T[a-zA-Z1-9]{33}$/', $address)) {
+            return [
+                'chat_id' => $chatId,
+                'text' => "地址输入不正确,请重新输入TRC20地址",
+                'reply_to_message_id' => $messageId,
+            ];
+        }
+        Cache::put("{$chatId}_address", $address);
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_ADDRESS_ALIAS);
+        return [
+            'chat_id' => $chatId,
+            'text' => "请输入地址别名,便于区分多个地址",
+        ];
+    }
+
+    //添加地址
+    public static function addAddress($chatId, $messageId)
+    {
+        $text = "请输入新的TRC20地址\n";
+        $keyboard = [[
+            ['text' => '❌取消', 'callback_data' => "message@@close"],
+        ]];
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_ADDRESS_TRC20);
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+
+    //地址管理
+    public static function getAddress($chatId, $messageId)
+    {
+        $text = "🏠 地址管理\n";
+        $text .= "--------------------------\n";
+
+        $list = Address::where('member_id', $chatId)
+            ->get();
+
+        $keyboard = [];
+        foreach ($list as $item) {
+            $keyboard[] = [['text' => $item->alias, 'callback_data' => "withdrawAddress@@detail{$item->id}"]];
+        }
+        if (count($list) < 5) {
+            $keyboard[] = [['text' => "➕ 添加地址", 'callback_data' => "withdrawAddress@@add"]];
+        }
+        $keyboard[] = [['text' => "↩️返回", 'callback_data' => "withdraw@@home"]];
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+    //设置提现地址
+    public function setAddress($chatId, $address)
+    {
+        $address = trim($address);
+        if (!$address) return [['chat_id' => $chatId, 'text' => "提现地址不能为空"]];
+        $user = User::where('member_id', $chatId)->first();
+        $user->usdt = $address;
+        $user->save();
+        $wallet = Wallet::where('member_id', $chatId)->first();
+        $temp = floatval($wallet->available_balance);
+        $text = "✅ 提现地址已设置\n\n";
+        $text .= "请发送提现金额\n";
+        $text .= "💰 当前可用USDT余额:{$temp}\n";
+        $text .= "⚠️ 提现将收取{$this->serviceCharge}U作为手续费\n";
+        $text .= "------------------------------------\n";
+        $text .= "例如您要提现100 USDT\n";
+        $text .= "那么请发送:【提现】100\n";
+        return [['chat_id' => $chatId, 'text' => $text]];
+    }
+
+
+    public static function done($chatId, $messageId, $firstName)
+    {
+        $serviceCharge = (new WithdrawService())->serviceCharge;
+        $amount = Cache::get("{$chatId}_WITHDRAW_MONEY", '');
+        $address = Cache::get("{$chatId}_WITHDRAW_ADDRESS", '');
+        if (!$amount || !$address) return WithdrawService::index($chatId, $firstName, $messageId);
+        $real = bcsub($amount, $serviceCharge, 10);
+        $real = floatval($real);
+
+        if ($amount <= $serviceCharge) {
+            return [
+                'chat_id' => $chatId,
+                'text' => "⚠️提现不能少于{$serviceCharge} USDT,请重试",
+                'message_id' => $messageId
+            ];
+        }
+
+        $wallet = Wallet::where('member_id', $chatId)->first();
+        $temp = floatval($wallet->available_balance);
+        if ($amount > $temp) {
+            return [
+                'chat_id' => $chatId,
+                'text' => "⚠️可用余额不足,请重试",
+                'message_id' => $messageId
+            ];
+        }
+
+
+        $ru = RoomUser::where('member_id', $chatId)
+            ->whereIn('status', [0, 1, 2, 3])
+            ->first();
+        if ($ru) {
+            $text = "游戏未结算\n";
+            $text .= "⚠️ 您还有进行中的游戏,所有游戏结算后方可提现\n";
+            return [
+                'chat_id' => $chatId,
+                'text' => $text,
+                'message_id' => $messageId,
+                'reply_markup' => json_encode(['inline_keyboard' => [[
+                    ['text' => '进入房间', 'callback_data' => "games@@home{$ru->room_id}"],
+                ]]])
+            ];
+        }
+
+
+        $wallet = Wallet::where('member_id', $chatId)->first();
+        $bl = new BalanceLog();
+        $bl->member_id = $chatId;
+        $bl->amount = $amount * -1;
+        $bl->before_balance = $wallet->available_balance;
+
+        $wallet->available_balance = bcsub($wallet->available_balance, $amount, 10);
+        $wallet->save();
+        $bl->after_balance = $wallet->available_balance;
+        $bl->change_type = '提现';
+
+
+        $withdraw = new Withdraw();
+        $withdraw->member_id = $chatId;
+        $withdraw->amount = $amount;
+        $withdraw->service_charge = $serviceCharge;
+        $withdraw->to_account = $real;
+        $withdraw->address = $address;
+        $withdraw->status = 0;
+        $withdraw->after_balance = $wallet->available_balance;
+        $withdraw->save();
+        $bl->related_id = $withdraw->id;
+        $bl->save();
+
+        $temp = floatval($wallet->available_balance);
+        $text = "✅ 提现申请已提交!\n\n";
+        $text .= "钱包余额:{$temp} USDT\n";
+        $text .= "提现金额:{$amount} USDT\n";
+        $text .= "实际到账:{$real} USDT\n";
+        $text .= "手续费:{$serviceCharge} USDT\n\n";
+        $text .= "⌛️请等待系统处理, 到账时间可能需要几分钟!\n";
+        Cache::delete(get_step_key($chatId));
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId,
+        ];
+    }
+
+
+    public static function chooseAddress($chatId, $firstName, $messageId, $id)
+    {
+        $serviceCharge = (new WithdrawService())->serviceCharge;
+        $amount = Cache::get("{$chatId}_WITHDRAW_MONEY", '');
+        if (!$amount) return WithdrawService::index($chatId, $firstName, $messageId);
+        $amount = floatval($amount);
+        $real = bcsub($amount, $serviceCharge, 10);
+        $real = floatval($real);
+        $address = Address::where('id', $id)
+            ->where('member_id', $chatId)->first();
+
+
+        $text = "请确认\n\n";
+        $text .= "手续费:{$serviceCharge} USDT\n";
+        $text .= "提现金额:{$amount} USDT\n";
+        $text .= "实际到账:{$real} USDT\n";
+        $text .= "提现地址:{$address->address}\n";
+
+        $keyboard = [
+            [
+                ['text' => '✅ 确认', 'callback_data' => "withdrawAddress@@done"],
+            ],
+            [
+                ['text' => '❌取消', 'callback_data' => "message@@close"]
+            ]
+        ];
+        Cache::put("{$chatId}_WITHDRAW_ADDRESS", $address->address);
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+
+    //用户输入提现金额
+    public function inputAmount($chatId, $amount, $messageId)
+    {
+        if (!preg_match('/^-?\d+(\.\d+)?$/', $amount)) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => "金额输入不正确,请发送提现数字",
+                'reply_to_message_id' => $messageId
+            ]];
+        }
+        $wallet = Wallet::where('member_id', $chatId)->first();
+        $temp = floatval($wallet->available_balance);
+        if ($amount <= $this->serviceCharge) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => "⚠️提现不能少于{$this->serviceCharge} USDT,请重试",
+                'reply_to_message_id' => $messageId
+            ]];
+        }
+        if ($amount > $temp) {
+            return [[
+                'chat_id' => $chatId,
+                'text' => "⚠️可用余额不足,请重试",
+                'reply_to_message_id' => $messageId
+            ]];
+        }
+        $ru = RoomUser::where('member_id', $chatId)
+            ->whereIn('status', [0, 1, 2, 3])
+            ->first();
+        if ($ru) {
+            $text = "*游戏未结算*\n";
+            $text .= "⚠️ 您还有进行中的游戏,所有游戏结算后方可提现\n";
+            return [[
+                'chat_id' => $chatId,
+                'text' => $text,
+                'parse_mode' => 'MarkdownV2',
+                'reply_markup' => json_encode(['inline_keyboard' => [[
+                    ['text' => '进入房间', 'callback_data' => "games@@home{$ru->room_id}"],
+                ]]])
+            ]];
+        }
+
+
+        $list = Address::where('member_id', $chatId)->get();
+        $keyboard = [];
+        foreach ($list as $item) {
+            $keyboard[] = [['text' => $item->alias, 'callback_data' => "withdrawAddress@@choose{$item->id}"]];
+        }
+        $keyboard[] = [
+            ['text' => '🏠 地址管理', 'callback_data' => "withdraw@@address"],
+            ['text' => '❌取消', 'callback_data' => "message@@close"]
+        ];
+
+        $text = "请直接选择下面的地址\n";
+        $text .= "⚠️提示:请务必确认提现地址正确无误,\n否则资金丢失将无法找回请自负!";
+        Cache::put("{$chatId}_WITHDRAW_MONEY", $amount);
+        Cache::put(get_step_key($chatId), StepStatus::CHOOSE_WITHDRAW_ADDRESS);
+        return [[
+            'chat_id' => $chatId,
+            'text' => $text,
+            'reply_to_message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ]];
+    }
+
+    //申请提现
+    public function apply($chatId, $messageId)
+    {
+        $wallet = Wallet::where('member_id', $chatId)->first();
+        $temp = floatval($wallet->available_balance);
+        $text = "请发送提现金额\n";
+        $text .= "💰 当前可用USDT余额:{$temp}\n";
+        $text .= "⚠️ 提现将收取{$this->serviceCharge}U作为手续费\n";
+//        $keyboard = [[
+//            ['text' => "🔙返回", 'callback_data' => "withdraw@@home"],
+//        ]];
+        Cache::put(get_step_key($chatId), StepStatus::INPUT_WITHDRAW_MONEY);
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId,
+//            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+
+    //提现管理
+    public static function index($chatId, $firstName, $messageId = null)
+    {
+        $wallet = Wallet::where('member_id', $chatId)->first();
+        $text = "👤 {$firstName}({$chatId})\n\n";
+        $text .= "💰钱包余额\n";
+        $temp = floatval($wallet->available_balance);
+        $text .= "USDT:{$temp}\n";
+        $text .= "--------------------------\n";
+        $serviceAccount = Config::where('field', 'service_account')->first()->val;
+        $keyboard = [
+            [
+                ['text' => '➕ 提现', 'callback_data' => "withdraw@@apply"],
+                ['text' => '🧾 账单', 'callback_data' => "withdraw@@bill"]
+            ],
+            [
+                ['text' => '🏠 地址管理', 'callback_data' => "withdraw@@address"],
+                ['text' => '👩 客服帮助', 'url' => "https://t.me/{$serviceAccount}"]
+            ],
+            [
+                ['text' => '❌取消', 'callback_data' => "message@@close"],
+            ]
+        ];
+
+        return [
+            'chat_id' => $chatId,
+            'text' => $text,
+            'message_id' => $messageId,
+            'reply_markup' => json_encode(['inline_keyboard' => $keyboard])
+        ];
+    }
+}

+ 53 - 0
artisan

@@ -0,0 +1,53 @@
+#!/usr/bin/env php
+<?php
+
+define('LARAVEL_START', microtime(true));
+
+/*
+|--------------------------------------------------------------------------
+| Register The Auto Loader
+|--------------------------------------------------------------------------
+|
+| Composer provides a convenient, automatically generated class loader
+| for our application. We just need to utilize it! We'll require it
+| into the script here so that we do not have to worry about the
+| loading of any of our classes manually. It's great to relax.
+|
+*/
+
+require __DIR__.'/vendor/autoload.php';
+
+$app = require_once __DIR__.'/bootstrap/app.php';
+
+/*
+|--------------------------------------------------------------------------
+| Run The Artisan Application
+|--------------------------------------------------------------------------
+|
+| When we run the console application, the current CLI command will be
+| executed in this console and the response sent back to a terminal
+| or another output device for the developers. Here goes nothing!
+|
+*/
+
+$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
+
+$status = $kernel->handle(
+    $input = new Symfony\Component\Console\Input\ArgvInput,
+    new Symfony\Component\Console\Output\ConsoleOutput
+);
+
+/*
+|--------------------------------------------------------------------------
+| Shutdown The Application
+|--------------------------------------------------------------------------
+|
+| Once Artisan has finished running, we will fire off the shutdown events
+| so that any final work may be done by the application before we shut
+| down the process. This is the last thing to happen to the request.
+|
+*/
+
+$kernel->terminate($input, $status);
+
+exit($status);

+ 55 - 0
bootstrap/app.php

@@ -0,0 +1,55 @@
+<?php
+
+/*
+|--------------------------------------------------------------------------
+| Create The Application
+|--------------------------------------------------------------------------
+|
+| The first thing we will do is create a new Laravel application instance
+| which serves as the "glue" for all the components of Laravel, and is
+| the IoC container for the system binding all of the various parts.
+|
+*/
+
+$app = new Illuminate\Foundation\Application(
+    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
+);
+
+/*
+|--------------------------------------------------------------------------
+| Bind Important Interfaces
+|--------------------------------------------------------------------------
+|
+| Next, we need to bind some important interfaces into the container so
+| we will be able to resolve them when needed. The kernels serve the
+| incoming requests to this application from both the web and CLI.
+|
+*/
+
+$app->singleton(
+    Illuminate\Contracts\Http\Kernel::class,
+    App\Http\Kernel::class
+);
+
+$app->singleton(
+    Illuminate\Contracts\Console\Kernel::class,
+    App\Console\Kernel::class
+);
+
+$app->singleton(
+    Illuminate\Contracts\Debug\ExceptionHandler::class,
+    App\Exceptions\Handler::class
+);
+
+/*
+|--------------------------------------------------------------------------
+| Return The Application
+|--------------------------------------------------------------------------
+|
+| This script returns the application instance. The instance is given to
+| the calling script so we can separate the building of the instances
+| from the actual running of the application and sending responses.
+|
+*/
+
+return $app;

+ 2 - 0
bootstrap/cache/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 85 - 0
composer.json

@@ -0,0 +1,85 @@
+{
+  "name": "laravel/laravel",
+  "type": "project",
+  "description": "The Laravel Framework.",
+  "keywords": [
+    "framework",
+    "laravel"
+  ],
+  "license": "MIT",
+  "require": {
+    "php": ">=7.1",
+    "ext-curl": "*",
+    "ext-gd": "*",
+    "ext-json": "*",
+    "endroid/qr-code": "^5.0",
+    "firebase/php-jwt": "^6.10",
+    "google/apiclient": "^2.18",
+    "guzzlehttp/guzzle": "^7.2",
+    "irazasyed/telegram-bot-sdk": "^3.15",
+    "jpush/jpush": "^3.6",
+    "kornrunner/keccak": "^1.1",
+    "kornrunner/secp256k1": "^0.3.0",
+    "laravel-lang/lang": "^14.7",
+    "laravel/framework": "^9.19",
+    "laravel/sanctum": "^3.3",
+    "laravel/tinker": "^2.7",
+    "phpseclib/phpseclib": "^3.0",
+    "simplesoftwareio/simple-qrcode": "^4.2",
+    "simplito/elliptic-php": "^1.0",
+    "stephenhill/base58": "^1.1"
+  },
+  "require-dev": {
+    "fakerphp/faker": "^1.9.1",
+    "laravel/pint": "^1.0",
+    "laravel/sail": "^1.0.1",
+    "mockery/mockery": "^1.4.4",
+    "nunomaduro/collision": "^6.1",
+    "phpunit/phpunit": "^9.5.10",
+    "spatie/laravel-ignition": "^1.0"
+  },
+  "autoload": {
+    "psr-4": {
+      "App\\": "app/",
+      "Database\\Factories\\": "database/factories/",
+      "Database\\Seeders\\": "database/seeders/",
+      "TronTool\\": "tron.php/src/"
+    },
+    "files": [
+      "app/Helpers/helpers.php"
+    ]
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Tests\\": "tests/"
+    }
+  },
+  "scripts": {
+    "post-autoload-dump": [
+      "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
+      "@php artisan package:discover --ansi"
+    ],
+    "post-update-cmd": [
+      "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
+    ],
+    "post-root-package-install": [
+      "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
+    ],
+    "post-create-project-cmd": [
+      "@php artisan key:generate --ansi"
+    ]
+  },
+  "extra": {
+    "laravel": {
+      "dont-discover": []
+    }
+  },
+  "config": {
+    "optimize-autoloader": true,
+    "preferred-install": "dist",
+    "sort-packages": true,
+    "allow-plugins": {
+      "pestphp/pest-plugin": true
+    }
+  }
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov