瀏覽代碼

Merge pull request #2085 from sspanel-uim/dev

Dev 20230722
M1Screw 2 年之前
父節點
當前提交
3355b8922b
共有 33 個文件被更改,包括 633 次插入514 次删除
  1. 15 15
      app/routes.php
  2. 43 42
      composer.lock
  3. 10 0
      config/settings.json
  4. 1 2
      db/2022.12_to_2022.12.1.sql
  5. 11 13
      db/migrations/2023020100-init.php
  6. 0 8
      db/migrations/2023061800-update_new_shop_data_type.php
  7. 48 0
      db/migrations/2023072000-add_sub_log.php
  8. 0 0
      resources/views/tabler/admin/log/subscribe.tpl
  9. 1 1
      resources/views/tabler/admin/node/edit.tpl
  10. 6 0
      resources/views/tabler/admin/setting/support.tpl
  11. 2 1
      resources/views/tabler/user/edit.tpl
  12. 1 1
      resources/views/tabler/user/subscribe_log.tpl
  13. 3 3
      src/Controllers/Admin/AnnController.php
  14. 3 3
      src/Controllers/Admin/DocsController.php
  15. 1 0
      src/Controllers/Admin/Setting/SupportController.php
  16. 6 8
      src/Controllers/Admin/SubscribeLogController.php
  17. 2 2
      src/Controllers/AuthController.php
  18. 2 2
      src/Controllers/LinkController.php
  19. 3 2
      src/Controllers/PasswordController.php
  20. 2 2
      src/Controllers/SubController.php
  21. 324 0
      src/Controllers/User/InfoController.php
  22. 3 3
      src/Controllers/User/LogController.php
  23. 10 9
      src/Controllers/User/TicketController.php
  24. 2 307
      src/Controllers/UserController.php
  25. 52 0
      src/Models/SubscribeLog.php
  26. 1 1
      src/Models/User.php
  27. 0 42
      src/Models/UserSubscribeLog.php
  28. 5 6
      src/Services/CronDetect.php
  29. 27 26
      src/Services/CronJob.php
  30. 1 1
      src/Services/Password.php
  31. 19 0
      src/Services/RateLimit.php
  32. 2 2
      src/Utils/Telegram.php
  33. 27 12
      src/Utils/Telegram/Callback.php

+ 15 - 15
app/routes.php

@@ -46,19 +46,22 @@ return static function (Slim\App $app): void {
         $group->get('/ticket/{id}/view', App\Controllers\User\TicketController::class . ':ticketView');
         $group->put('/ticket/{id}', App\Controllers\User\TicketController::class . ':ticketUpdate');
         // 资料编辑
-        $group->get('/edit', App\Controllers\UserController::class . ':edit');
-        $group->post('/email', App\Controllers\UserController::class . ':updateEmail');
-        $group->post('/username', App\Controllers\UserController::class . ':updateUsername');
-        $group->post('/password', App\Controllers\UserController::class . ':updatePassword');
+        $group->get('/edit', App\Controllers\User\InfoController::class . ':index');
+        $group->post('/email', App\Controllers\User\InfoController::class . ':updateEmail');
+        $group->post('/username', App\Controllers\User\InfoController::class . ':updateUsername');
+        $group->post('/password', App\Controllers\User\InfoController::class . ':updatePassword');
+        $group->post('/contact_update', App\Controllers\User\InfoController::class . ':updateContact');
+        $group->post('/theme', App\Controllers\User\InfoController::class . ':updateTheme');
+        $group->post('/daily_mail', App\Controllers\User\InfoController::class . ':updateDailyMail');
+        $group->post('/passwd_reset', App\Controllers\User\InfoController::class . ':resetPasswd');
+        $group->post('/apitoken_reset', App\Controllers\User\InfoController::class . ':resetApiToken');
+        $group->post('/method', App\Controllers\User\InfoController::class . ':updateMethod');
+        $group->post('/url_reset', App\Controllers\User\InfoController::class . ':resetURL');
+        $group->put('/invite', App\Controllers\User\InfoController::class . ':resetInviteURL');
+        $group->post('/kill', App\Controllers\User\InfoController::class . ':sendToGulag');
+        // 发送验证邮件
         $group->post('/send', App\Controllers\AuthController::class . ':sendVerify');
-        $group->post('/contact_update', App\Controllers\UserController::class . ':updateContact');
-        $group->post('/theme', App\Controllers\UserController::class . ':updateTheme');
-        $group->post('/mail', App\Controllers\UserController::class . ':updateMail');
-        $group->post('/passwd_reset', App\Controllers\UserController::class . ':resetPasswd');
-        $group->post('/apitoken_reset', App\Controllers\UserController::class . ':resetApiToken');
-        $group->post('/method', App\Controllers\UserController::class . ':updateMethod');
-        $group->get('/kill', App\Controllers\UserController::class . ':kill');
-        $group->post('/kill', App\Controllers\UserController::class . ':handleKill');
+        // 登出
         $group->get('/logout', App\Controllers\UserController::class . ':logout');
         // MFA
         $group->post('/ga_check', App\Controllers\User\MFAController::class . ':checkGa');
@@ -66,9 +69,6 @@ return static function (Slim\App $app): void {
         $group->post('/ga_reset', App\Controllers\User\MFAController::class . ':resetGa');
         // Telegram
         $group->post('/telegram_reset', App\Controllers\UserController::class . ':resetTelegram');
-        // URL
-        $group->post('/url_reset', App\Controllers\UserController::class . ':resetURL');
-        $group->put('/invite', App\Controllers\UserController::class . ':resetInviteURL');
         // 深色模式切换
         $group->post('/switch_theme_mode', App\Controllers\UserController::class . ':switchThemeMode');
         // 记录

+ 43 - 42
composer.lock

@@ -123,16 +123,16 @@
         },
         {
             "name": "aws/aws-sdk-php",
-            "version": "3.275.7",
+            "version": "3.276.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/aws/aws-sdk-php.git",
-                "reference": "54dcef3349c81b46c0f5f6e54b5f9bfb5db19903"
+                "reference": "c8b38cfab677a3ec98e3f40bccd1a2bf851a014f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/54dcef3349c81b46c0f5f6e54b5f9bfb5db19903",
-                "reference": "54dcef3349c81b46c0f5f6e54b5f9bfb5db19903",
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c8b38cfab677a3ec98e3f40bccd1a2bf851a014f",
+                "reference": "c8b38cfab677a3ec98e3f40bccd1a2bf851a014f",
                 "shasum": ""
             },
             "require": {
@@ -212,9 +212,9 @@
             "support": {
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.275.7"
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.276.3"
             },
-            "time": "2023-07-13T18:21:04+00:00"
+            "time": "2023-07-21T18:30:18+00:00"
         },
         {
             "name": "bacon/bacon-qr-code",
@@ -1992,16 +1992,16 @@
         },
         {
             "name": "mailgun/mailgun-php",
-            "version": "v3.5.8",
+            "version": "v3.5.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/mailgun/mailgun-php.git",
-                "reference": "5039483324111a2c3356d25dffb26699339d00ee"
+                "reference": "181749e4daea67cb33cdb27ac422261d3601aeb9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/mailgun/mailgun-php/zipball/5039483324111a2c3356d25dffb26699339d00ee",
-                "reference": "5039483324111a2c3356d25dffb26699339d00ee",
+                "url": "https://api.github.com/repos/mailgun/mailgun-php/zipball/181749e4daea67cb33cdb27ac422261d3601aeb9",
+                "reference": "181749e4daea67cb33cdb27ac422261d3601aeb9",
                 "shasum": ""
             },
             "require": {
@@ -2046,9 +2046,9 @@
             "description": "The Mailgun SDK provides methods for all API functions.",
             "support": {
                 "issues": "https://github.com/mailgun/mailgun-php/issues",
-                "source": "https://github.com/mailgun/mailgun-php/tree/v3.5.8"
+                "source": "https://github.com/mailgun/mailgun-php/tree/v3.5.9"
             },
-            "time": "2023-06-23T15:24:18+00:00"
+            "time": "2023-07-19T11:28:29+00:00"
         },
         {
             "name": "maxmind-db/reader",
@@ -2630,16 +2630,16 @@
         },
         {
             "name": "openai-php/client",
-            "version": "v0.6.3",
+            "version": "v0.6.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/openai-php/client.git",
-                "reference": "b703dba3bee84c68f14d454f6e30170774d11734"
+                "reference": "6e0738a3f052fb0d27bd57136fff47db96248f65"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/openai-php/client/zipball/b703dba3bee84c68f14d454f6e30170774d11734",
-                "reference": "b703dba3bee84c68f14d454f6e30170774d11734",
+                "url": "https://api.github.com/repos/openai-php/client/zipball/6e0738a3f052fb0d27bd57136fff47db96248f65",
+                "reference": "6e0738a3f052fb0d27bd57136fff47db96248f65",
                 "shasum": ""
             },
             "require": {
@@ -2702,7 +2702,7 @@
             ],
             "support": {
                 "issues": "https://github.com/openai-php/client/issues",
-                "source": "https://github.com/openai-php/client/tree/v0.6.3"
+                "source": "https://github.com/openai-php/client/tree/v0.6.4"
             },
             "funding": [
                 {
@@ -2718,7 +2718,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-07-07T12:49:58+00:00"
+            "time": "2023-07-18T22:59:43+00:00"
         },
         {
             "name": "ozdemir/datatables",
@@ -4639,16 +4639,16 @@
         },
         {
             "name": "smarty/smarty",
-            "version": "v4.3.1",
+            "version": "v4.3.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/smarty-php/smarty.git",
-                "reference": "e28cb0915b4e3749bf57d4ebae2984e25395cfe5"
+                "reference": "1d9cda2be34fd6edb74924684260636fd0b89288"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/smarty-php/smarty/zipball/e28cb0915b4e3749bf57d4ebae2984e25395cfe5",
-                "reference": "e28cb0915b4e3749bf57d4ebae2984e25395cfe5",
+                "url": "https://api.github.com/repos/smarty-php/smarty/zipball/1d9cda2be34fd6edb74924684260636fd0b89288",
+                "reference": "1d9cda2be34fd6edb74924684260636fd0b89288",
                 "shasum": ""
             },
             "require": {
@@ -4699,9 +4699,9 @@
             "support": {
                 "forum": "https://github.com/smarty-php/smarty/discussions",
                 "issues": "https://github.com/smarty-php/smarty/issues",
-                "source": "https://github.com/smarty-php/smarty/tree/v4.3.1"
+                "source": "https://github.com/smarty-php/smarty/tree/v4.3.2"
             },
-            "time": "2023-03-28T19:47:03+00:00"
+            "time": "2023-07-19T10:27:36+00:00"
         },
         {
             "name": "srmklive/paypal",
@@ -4813,16 +4813,16 @@
         },
         {
             "name": "stripe/stripe-php",
-            "version": "v10.17.0",
+            "version": "v10.18.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/stripe/stripe-php.git",
-                "reference": "73383b44de153f4d602b90dad3ef075259bcecef"
+                "reference": "c32549e443a619d1b885b99d624568ecae82d6a0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/stripe/stripe-php/zipball/73383b44de153f4d602b90dad3ef075259bcecef",
-                "reference": "73383b44de153f4d602b90dad3ef075259bcecef",
+                "url": "https://api.github.com/repos/stripe/stripe-php/zipball/c32549e443a619d1b885b99d624568ecae82d6a0",
+                "reference": "c32549e443a619d1b885b99d624568ecae82d6a0",
                 "shasum": ""
             },
             "require": {
@@ -4868,9 +4868,9 @@
             ],
             "support": {
                 "issues": "https://github.com/stripe/stripe-php/issues",
-                "source": "https://github.com/stripe/stripe-php/tree/v10.17.0"
+                "source": "https://github.com/stripe/stripe-php/tree/v10.18.0"
             },
-            "time": "2023-07-13T22:12:18+00:00"
+            "time": "2023-07-20T16:59:49+00:00"
         },
         {
             "name": "symfony/console",
@@ -8364,16 +8364,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "10.2.5",
+            "version": "10.2.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd"
+                "reference": "1c17815c129f133f3019cc18e8d0c8622e6d9bcd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd",
-                "reference": "15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c17815c129f133f3019cc18e8d0c8622e6d9bcd",
+                "reference": "1c17815c129f133f3019cc18e8d0c8622e6d9bcd",
                 "shasum": ""
             },
             "require": {
@@ -8445,7 +8445,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.5"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.6"
             },
             "funding": [
                 {
@@ -8461,7 +8461,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-07-14T04:18:47+00:00"
+            "time": "2023-07-17T12:08:28+00:00"
         },
         {
             "name": "psr/cache",
@@ -9022,16 +9022,16 @@
         },
         {
             "name": "sebastian/global-state",
-            "version": "6.0.0",
+            "version": "6.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/global-state.git",
-                "reference": "aab257c712de87b90194febd52e4d184551c2d44"
+                "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/aab257c712de87b90194febd52e4d184551c2d44",
-                "reference": "aab257c712de87b90194febd52e4d184551c2d44",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/7ea9ead78f6d380d2a667864c132c2f7b83055e4",
+                "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4",
                 "shasum": ""
             },
             "require": {
@@ -9071,7 +9071,8 @@
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/global-state/issues",
-                "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.0"
+                "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+                "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.1"
             },
             "funding": [
                 {
@@ -9079,7 +9080,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-02-03T07:07:38+00:00"
+            "time": "2023-07-19T07:19:23+00:00"
         },
         {
             "name": "sebastian/lines-of-code",

+ 10 - 0
config/settings.json

@@ -709,6 +709,16 @@
         "default": "1",
         "mark": "启用工单邮件提醒"
     },
+    {
+        "id": null,
+        "item": "ticket_limit",
+        "value": "3",
+        "class": "support",
+        "is_public": 0,
+        "type": "int",
+        "default": "3",
+        "mark": "用戶工单配額(每月)"
+    },
     {
         "id": null,
         "item": "reg_mode",

+ 1 - 2
db/2022.12_to_2022.12.1.sql

@@ -8,5 +8,4 @@ ALTER TABLE link DROP FOREIGN KEY link_ibfk_1;
 ALTER TABLE login_ip DROP FOREIGN KEY login_ip_ibfk_1;
 ALTER TABLE paylist DROP FOREIGN KEY paylist_ibfk_1;
 ALTER TABLE user_hourly_usage DROP FOREIGN KEY user_hourly_usage_ibfk_1;
-ALTER TABLE user_invite_code DROP FOREIGN KEY user_invite_code_ibfk_1;
-ALTER TABLE user_subscribe_log DROP FOREIGN KEY user_subscribe_log_ibfk_1;
+ALTER TABLE user_invite_code DROP FOREIGN KEY user_invite_code_ibfk_1;

+ 11 - 13
db/migrations/2023020100-init.php

@@ -233,6 +233,17 @@ return new class() implements MigrationInterface {
                 KEY `type` (`type`),
                 KEY `status` (`status`)
             ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+            
+            CREATE TABLE `subscribe_log` (
+                `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID',
+                `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户ID',
+                `type` varchar(255) NOT NULL DEFAULT '' COMMENT '获取的订阅类型',
+                `request_ip` varchar(255) NOT NULL DEFAULT '' COMMENT '请求IP',
+                `request_user_agent` varchar(255) NOT NULL DEFAULT '' COMMENT '请求UA信息',
+                `request_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '请求时间',
+                PRIMARY KEY (`id`),
+                KEY `user_id` (`user_id`)
+            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 
             CREATE TABLE `ticket` (
                 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '工单ID',
@@ -354,19 +365,6 @@ return new class() implements MigrationInterface {
                 `create_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '创建时间',
                 PRIMARY KEY (`id`),
                 KEY `user_id` (`user_id`)
-            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-
-            CREATE TABLE `user_subscribe_log` (
-                `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID',
-                `user_name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户名',
-                `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户ID',
-                `email` varchar(255) NOT NULL DEFAULT '' COMMENT '用户邮箱',
-                `subscribe_type` varchar(255) NOT NULL DEFAULT '' COMMENT '获取的订阅类型',
-                `request_ip` varchar(255) NOT NULL DEFAULT '' COMMENT '请求IP',
-                `request_time` timestamp NOT NULL DEFAULT '1989-06-04 00:05:00' COMMENT '请求时间',
-                `request_user_agent` varchar(255) NOT NULL DEFAULT '' COMMENT '请求UA信息',
-                PRIMARY KEY (`id`),
-                KEY `user_id` (`user_id`)
             ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"
         );
 

+ 0 - 8
db/migrations/2023061800-update_new_shop_data_type.php

@@ -168,14 +168,6 @@ return new class() implements MigrationInterface {
         ALTER TABLE user_invite_code MODIFY COLUMN `created_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '创建时间';
         ALTER TABLE user_invite_code MODIFY COLUMN `updated_at` timestamp NOT NULL DEFAULT '1989-06-04 00:05:00' COMMENT '更新时间';
         ALTER TABLE user_money_log MODIFY COLUMN `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID';
-        ALTER TABLE user_subscribe_log MODIFY COLUMN `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID';
-        ALTER TABLE user_subscribe_log MODIFY COLUMN `user_name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户名';
-        ALTER TABLE user_subscribe_log MODIFY COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户ID';
-        ALTER TABLE user_subscribe_log MODIFY COLUMN `email` varchar(255) NOT NULL DEFAULT '' COMMENT '用户邮箱';
-        ALTER TABLE user_subscribe_log MODIFY COLUMN `subscribe_type` varchar(255) NOT NULL DEFAULT '' COMMENT '获取的订阅类型';
-        ALTER TABLE user_subscribe_log MODIFY COLUMN `request_ip` varchar(255) NOT NULL DEFAULT '' COMMENT '请求IP';
-        ALTER TABLE user_subscribe_log MODIFY COLUMN `request_time` timestamp NOT NULL DEFAULT '1989-06-04 00:05:00' COMMENT '请求时间';
-        ALTER TABLE user_subscribe_log MODIFY COLUMN `request_user_agent` varchar(255) NOT NULL DEFAULT '' COMMENT '请求UA信息';
         SET FOREIGN_KEY_CHECKS = 1;");
 
         return 2023061800;

+ 48 - 0
db/migrations/2023072000-add_sub_log.php

@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+use App\Interfaces\MigrationInterface;
+use App\Services\DB;
+
+return new class() implements MigrationInterface {
+    public function up(): int
+    {
+        DB::getPdo()->exec("
+            CREATE TABLE IF NOT EXISTS `subscribe_log` (
+                `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID',
+                `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户ID',
+                `type` varchar(255) NOT NULL DEFAULT '' COMMENT '获取的订阅类型',
+                `request_ip` varchar(255) NOT NULL DEFAULT '' COMMENT '请求IP',
+                `request_user_agent` varchar(255) NOT NULL DEFAULT '' COMMENT '请求UA信息',
+                `request_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '请求时间',
+                PRIMARY KEY (`id`),
+                KEY `user_id` (`user_id`)
+            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+            DROP TABLE IF EXISTS `user_subscribe_log`;
+        ");
+
+        return 2023072000;
+    }
+
+    public function down(): int
+    {
+        DB::getPdo()->exec("
+            CREATE TABLE IF NOT EXISTS `user_subscribe_log` (
+                `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID',
+                `user_name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户名',
+                `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户ID',
+                `email` varchar(255) NOT NULL DEFAULT '' COMMENT '用户邮箱',
+                `subscribe_type` varchar(255) NOT NULL DEFAULT '' COMMENT '获取的订阅类型',
+                `request_ip` varchar(255) NOT NULL DEFAULT '' COMMENT '请求IP',
+                `request_time` timestamp NOT NULL DEFAULT '1989-06-04 00:05:00' COMMENT '请求时间',
+                `request_user_agent` varchar(255) NOT NULL DEFAULT '' COMMENT '请求UA信息',
+                PRIMARY KEY (`id`),
+                KEY `user_id` (`user_id`)
+            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+            DROP TABLE IF EXISTS `subscribe_log`;
+        ");
+
+        return 2023071700;
+    }
+};

+ 0 - 0
resources/views/tabler/admin/subscribe.tpl → resources/views/tabler/admin/log/subscribe.tpl


+ 1 - 1
resources/views/tabler/admin/node/edit.tpl

@@ -165,7 +165,7 @@
                                     </div>
                                 </div>
                                 <label class="form-label col-form-label">
-                                    通讯密钥用于 gRPC API 鉴权,如需更改请点击重置
+                                    通讯密钥用于 WebAPI 节点模式鉴权,如需更改请点击重置
                                 </label>
                             </div>
                         </div>

+ 6 - 0
resources/views/tabler/admin/setting/support.tpl

@@ -100,6 +100,12 @@
                                         </select>
                                     </div>
                                 </div>
+                                <div class="form-group mb-3 row">
+                                    <label class="form-label col-3 col-form-label">用戶工单配額(每月)</label>
+                                    <div class="col">
+                                        <input id="ticket_limit" type="text" class="form-control" value="{$settings['ticket_limit']}">
+                                    </div>
+                                </div>
                             </div>
                         </div>
                     </div>

+ 2 - 1
resources/views/tabler/user/edit.tpl

@@ -647,7 +647,7 @@
         $("#modify-daily-report").click(function() {
             $.ajax({
                 type: "POST",
-                url: "/user/mail",
+                url: "/user/daily_mail",
                 dataType: "json",
                 data: {
                     mail: $('#daily-report').val()
@@ -780,6 +780,7 @@
                 }
             })
         });
+
         $("#unbind-telegram-btn").click(function() {
           $.ajax({
             type: "POST",

+ 1 - 1
resources/views/tabler/user/subscribe_log.tpl

@@ -36,7 +36,7 @@
                                     {foreach $logs as $log}
                                     <tr>
                                         <td>#{$log->id}</td>
-                                        <td>{$log->subscribe_type}</td>
+                                        <td>{$log->type}</td>
                                         <td>{$log->request_ip}</td>
                                         <td>{$log->location}</td>
                                         <td>{$log->request_time}</td>

+ 3 - 3
src/Controllers/Admin/AnnController.php

@@ -8,12 +8,12 @@ use App\Controllers\BaseController;
 use App\Models\Ann;
 use App\Models\User;
 use App\Utils\Telegram;
+use App\Utils\Tools;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 use Telegram\Bot\Exceptions\TelegramSDKException;
-use function date;
 use function str_replace;
 use function strip_tags;
 use const PHP_EOL;
@@ -84,7 +84,7 @@ final class AnnController extends BaseController
 
         if ($content !== '') {
             $ann = new Ann();
-            $ann->date = date('Y-m-d H:i:s');
+            $ann->date = Tools::toDateTime(time());
             $ann->content = $content;
 
             if (! $ann->save()) {
@@ -154,7 +154,7 @@ final class AnnController extends BaseController
     {
         $ann = Ann::find($args['id']);
         $ann->content = (string) $request->getParam('content');
-        $ann->date = date('Y-m-d H:i:s');
+        $ann->date = Tools::toDateTime(time());
 
         if (! $ann->save()) {
             return $response->withJson([

+ 3 - 3
src/Controllers/Admin/DocsController.php

@@ -8,12 +8,12 @@ use App\Controllers\BaseController;
 use App\Models\Docs;
 use App\Services\ChatGPT;
 use App\Services\PaLM;
+use App\Utils\Tools;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 use Telegram\Bot\Exceptions\TelegramSDKException;
-use function date;
 
 final class DocsController extends BaseController
 {
@@ -70,7 +70,7 @@ final class DocsController extends BaseController
         }
 
         $doc = new Docs();
-        $doc->date = date('Y-m-d H:i:s');
+        $doc->date = Tools::toDateTime(time());
         $doc->title = $title;
         $doc->content = $content;
 
@@ -139,7 +139,7 @@ final class DocsController extends BaseController
         $doc = Docs::find($args['id']);
         $doc->title = $request->getParam('title');
         $doc->content = $request->getParam('content');
-        $doc->date = date('Y-m-d H:i:s');
+        $doc->date = Tools::toDateTime(time());
 
         if (! $doc->save()) {
             return $response->withJson([

+ 1 - 0
src/Controllers/Admin/Setting/SupportController.php

@@ -20,6 +20,7 @@ final class SupportController extends BaseController
         // Ticket
         'enable_ticket',
         'mail_ticket',
+        'ticket_limit',
     ];
 
     /**

+ 6 - 8
src/Controllers/Admin/SubscribeLogController.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 namespace App\Controllers\Admin;
 
 use App\Controllers\BaseController;
-use App\Models\UserSubscribeLog;
+use App\Models\SubscribeLog;
 use App\Utils\Tools;
 use Exception;
 use GeoIp2\Exception\AddressNotFoundException;
@@ -20,10 +20,8 @@ final class SubscribeLogController extends BaseController
     [
         'field' => [
             'id' => '事件ID',
-            'user_name' => '用户名',
             'user_id' => '用户ID',
-            'email' => '用户邮箱',
-            'subscribe_type' => '获取的订阅类型',
+            'type' => '获取的订阅类型',
             'request_ip' => '请求IP',
             'location' => 'IP归属地',
             'request_time' => '请求时间',
@@ -41,7 +39,7 @@ final class SubscribeLogController extends BaseController
         return $response->write(
             $this->view()
                 ->assign('details', self::$details)
-                ->fetch('admin/subscribe.tpl')
+                ->fetch('admin/log/subscribe.tpl')
         );
     }
 
@@ -57,11 +55,11 @@ final class SubscribeLogController extends BaseController
         $page = $request->getParam('start') / $length + 1;
         $draw = $request->getParam('draw');
 
-        $subscribes = UserSubscribeLog::orderBy('id', 'desc')->paginate($length, '*', '', $page);
-        $total = UserSubscribeLog::count();
+        $subscribes = SubscribeLog::orderBy('id', 'desc')->paginate($length, '*', '', $page);
+        $total = SubscribeLog::count();
 
         foreach ($subscribes as $subscribe) {
-            $subscribe->location = Tools::getIpLocation($subscribe->request_ip);
+            $subscribe->request_time = Tools::toDateTime($subscribe->request_time);
         }
 
         return $response->withJson([

+ 2 - 2
src/Controllers/AuthController.php

@@ -191,7 +191,7 @@ final class AuthController extends BaseController
 
             $code = Tools::genRandomChar(6);
             $redis = Cache::initRedis();
-            $redis->setex($code, Setting::obtain('email_verify_code_ttl'), $email);
+            $redis->setex('email_verify:' . $code, Setting::obtain('email_verify_code_ttl'), $email);
 
             try {
                 Mail::send(
@@ -407,7 +407,7 @@ final class AuthController extends BaseController
         if (Setting::obtain('reg_email_verify')) {
             $redis = Cache::initRedis();
             $email_verify_code = trim($antiXss->xss_clean($request->getParam('emailcode')));
-            $email_verify = $redis->get($email_verify_code);
+            $email_verify = $redis->get('email_verify:' . $email_verify_code);
 
             if (! $email_verify) {
                 return ResponseHelper::error($response, '你的邮箱验证码不正确');

+ 2 - 2
src/Controllers/LinkController.php

@@ -7,7 +7,7 @@ namespace App\Controllers;
 use App\Models\Link;
 use App\Models\Node;
 use App\Models\Setting;
-use App\Models\UserSubscribeLog;
+use App\Models\SubscribeLog;
 use App\Services\RateLimit;
 use App\Utils\ResponseHelper;
 use Psr\Http\Message\ResponseInterface;
@@ -81,7 +81,7 @@ final class LinkController extends BaseController
 
         // 记录订阅日志
         if ($_ENV['subscribeLog']) {
-            UserSubscribeLog::addSubscribeLog($user, $sub_type, $request->getHeaderLine('User-Agent'));
+            SubscribeLog::add($user, $sub_type, $request->getHeaderLine('User-Agent'));
         }
 
         $sub_details = ' upload=' . $user->u

+ 3 - 2
src/Controllers/PasswordController.php

@@ -94,7 +94,7 @@ final class PasswordController extends BaseController
         $antiXss = new AntiXSS();
         $token = $antiXss->xss_clean($args['token']);
         $redis = Cache::initRedis();
-        $email = $redis->get($token);
+        $email = $redis->get('password_reset:' . $token);
 
         if (! $email) {
             return $response->withStatus(302)->withHeader('Location', '/password/reset');
@@ -124,13 +124,14 @@ final class PasswordController extends BaseController
         }
 
         $redis = Cache::initRedis();
-        $email = $redis->get($token);
+        $email = $redis->get('password_reset:' . $token);
 
         if (! $email) {
             return ResponseHelper::error($response, '链接无效');
         }
 
         $user = User::where('email', $email)->first();
+
         if ($user === null) {
             return ResponseHelper::error($response, '链接无效');
         }

+ 2 - 2
src/Controllers/SubController.php

@@ -7,7 +7,7 @@ namespace App\Controllers;
 use App\Models\Link;
 use App\Models\Node;
 use App\Models\Setting;
-use App\Models\UserSubscribeLog;
+use App\Models\SubscribeLog;
 use App\Services\RateLimit;
 use App\Utils\ResponseHelper;
 use App\Utils\Tools;
@@ -67,7 +67,7 @@ final class SubController extends BaseController
         };
 
         if ($_ENV['subscribeLog']) {
-            UserSubscribeLog::addSubscribeLog($user, $subtype, $request->getHeaderLine('User-Agent'));
+            SubscribeLog::add($user, $subtype, $request->getHeaderLine('User-Agent'));
         }
 
         if (in_array($subtype, ['json', 'sip008'])) {

+ 324 - 0
src/Controllers/User/InfoController.php

@@ -0,0 +1,324 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Controllers\User;
+
+use App\Controllers\BaseController;
+use App\Models\Setting;
+use App\Models\User;
+use App\Services\Auth;
+use App\Services\Cache;
+use App\Services\Config;
+use App\Services\MFA;
+use App\Utils\Hash;
+use App\Utils\ResponseHelper;
+use App\Utils\Telegram;
+use App\Utils\Tools;
+use Exception;
+use Psr\Http\Message\ResponseInterface;
+use Ramsey\Uuid\Uuid;
+use RedisException;
+use Slim\Http\Response;
+use Slim\Http\ServerRequest;
+use voku\helper\AntiXSS;
+use function in_array;
+use function strlen;
+use function strtolower;
+use const BASE_PATH;
+
+/**
+ *  InfoController
+ */
+final class InfoController extends BaseController
+{
+    /**
+     * @throws Exception
+     */
+    public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $themes = Tools::getDir(BASE_PATH . '/resources/views');
+        $bind_token = Telegram::addBindSession($this->user);
+        $methods = Config::getSupportParam('method');
+        $gaurl = MFA::getGAurl($this->user);
+
+        return $response->write($this->view()
+            ->assign('user', $this->user)
+            ->assign('themes', $themes)
+            ->assign('bind_token', $bind_token)
+            ->assign('methods', $methods)
+            ->assign('gaurl', $gaurl)
+            ->assign('telegram_bot', $_ENV['telegram_bot'])
+            ->registerClass('Config', Config::class)
+            ->fetch('user/edit.tpl'));
+    }
+
+    /**
+     * @throws RedisException
+     */
+    public function updateEmail(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $antiXss = new AntiXSS();
+        $new_email = $antiXss->xss_clean($request->getParam('newemail'));
+        $user = $this->user;
+        $old_email = $user->email;
+
+        if (! $_ENV['enable_change_email'] || $user->is_shadow_banned) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        if ($new_email === '') {
+            return ResponseHelper::error($response, '未填写邮箱');
+        }
+
+        if (! Tools::isEmailLegal($new_email)) {
+            return $response->withJson($check_res);
+        }
+
+        $exist_user = User::where('email', $new_email)->first();
+
+        if ($exist_user !== null) {
+            return ResponseHelper::error($response, '邮箱已经被使用了');
+        }
+
+        if ($new_email === $old_email) {
+            return ResponseHelper::error($response, '新邮箱不能和旧邮箱一样');
+        }
+
+        if (Setting::obtain('reg_email_verify')) {
+            $redis = Cache::initRedis();
+            $email_verify_code = $request->getParam('emailcode');
+            $email_verify = $redis->get('email_verify:' . $email_verify_code);
+
+            if (! $email_verify) {
+                return ResponseHelper::error($response, '你的邮箱验证码不正确');
+            }
+
+            $redis->del($email_verify_code);
+        }
+
+        $user->email = $new_email;
+
+        if (! $user->save()) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        return ResponseHelper::successfully($response, '修改成功');
+    }
+
+    public function updateUsername(ServerRequest $request, Response $response, array $args): ResponseInterface
+    {
+        $antiXss = new AntiXSS();
+        $newusername = $antiXss->xss_clean($request->getParam('newusername'));
+        $user = $this->user;
+
+        if ($user->is_shadow_banned) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        $user->user_name = $newusername;
+
+        if (! $user->save()) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        return ResponseHelper::successfully($response, '修改成功');
+    }
+
+    public function updatePassword(ServerRequest $request, Response $response, array $args): ResponseInterface
+    {
+        $oldpwd = $request->getParam('oldpwd');
+        $pwd = $request->getParam('pwd');
+        $repwd = $request->getParam('repwd');
+        $user = $this->user;
+
+        if ($oldpwd === '' || $pwd === '' || $repwd === '') {
+            return ResponseHelper::error($response, '密码不能为空');
+        }
+
+        if (! Hash::checkPassword($user->pass, $oldpwd)) {
+            return ResponseHelper::error($response, '旧密码错误');
+        }
+
+        if ($pwd !== $repwd) {
+            return ResponseHelper::error($response, '两次输入不符合');
+        }
+
+        if (strlen($pwd) < 8) {
+            return ResponseHelper::error($response, '密码太短啦');
+        }
+
+        $hashPwd = Hash::passwordHash($pwd);
+        $user->pass = $hashPwd;
+
+        if (! $user->save()) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        if (Setting::obtain('enable_forced_replacement')) {
+            $user->cleanLink();
+        }
+
+        return ResponseHelper::successfully($response, '修改成功');
+    }
+
+    public function updateContact(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $antiXss = new AntiXSS();
+        $type = $antiXss->xss_clean($request->getParam('imtype'));
+        $value = $antiXss->xss_clean($request->getParam('imvalue'));
+        $user = $this->user;
+
+        if ($user->telegram_id !== null || $user->is_shadow_banned) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        if ($value === '' || $type === '') {
+            return ResponseHelper::error($response, '联络方式不能为空');
+        }
+
+        if (User::where('im_value', $value)->where('im_type', $type)->first() !== null) {
+            return ResponseHelper::error($response, '此联络方式已经被注册');
+        }
+
+        $user->im_type = $type;
+        $user->im_value = $value;
+
+        if (! $user->save()) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        return ResponseHelper::successfully($response, '修改成功');
+    }
+
+    public function updateTheme(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $antiXss = new AntiXSS();
+        $theme = $antiXss->xss_clean($request->getParam('theme'));
+        $user = $this->user;
+
+        if ($theme === '') {
+            return ResponseHelper::error($response, '主题不能为空');
+        }
+
+        $user->theme = $theme;
+
+        if (! $user->save()) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        return ResponseHelper::successfully($response, '修改成功');
+    }
+
+    public function updateDailyMail(ServerRequest $request, Response $response, array $args): ResponseInterface
+    {
+        $value = (int) $request->getParam('mail');
+
+        if (! in_array($value, [0, 1, 2])) {
+            return ResponseHelper::error($response, '参数错误');
+        }
+
+        $user = $this->user;
+
+        if ($value === 2 && ! $_ENV['enable_telegram']) {
+            return ResponseHelper::error(
+                $response,
+                '修改失败,当前无法使用 Telegram 接收每日报告'
+            );
+        }
+
+        $user->daily_mail_enable = $value;
+
+        if (! $user->save()) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        return ResponseHelper::successfully($response, '修改成功');
+    }
+
+    public function resetPasswd(ServerRequest $request, Response $response, array $args): ResponseInterface
+    {
+        $user = $this->user;
+        $user->uuid = Uuid::uuid4();
+        $user->passwd = Tools::genRandomChar(16);
+
+        if (! $user->save()) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        return ResponseHelper::successfully($response, '修改成功');
+    }
+
+    public function resetApiToken(ServerRequest $request, Response $response, array $args): ResponseInterface
+    {
+        $user = $this->user;
+        $user->api_token = Uuid::uuid4();
+
+        if (! $user->save()) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        return ResponseHelper::successfully($response, '修改成功');
+    }
+
+    public function updateMethod(ServerRequest $request, Response $response, array $args): ResponseInterface
+    {
+        $antiXss = new AntiXSS();
+
+        $user = $this->user;
+        $method = strtolower($antiXss->xss_clean($request->getParam('method')));
+
+        if ($method === '') {
+            ResponseHelper::error($response, '非法输入');
+        }
+        if (! Tools::isParamValidate('method', $method)) {
+            ResponseHelper::error($response, '加密无效');
+        }
+
+        $user->method = $method;
+
+        if (! $user->save()) {
+            return ResponseHelper::error($response, '修改失败');
+        }
+
+        return ResponseHelper::successfully($response, '修改成功');
+    }
+
+    public function resetURL(ServerRequest $request, Response $response, array $args): ResponseInterface
+    {
+        $user = $this->user;
+        $user->cleanLink();
+
+        return ResponseHelper::successfully($response, '重置成功');
+    }
+
+    public function resetInviteURL(ServerRequest $request, Response $response, array $args): ResponseInterface
+    {
+        $user = $this->user;
+        $user->clearInviteCodes();
+
+        return ResponseHelper::successfully($response, '重置成功');
+    }
+
+    public function sendToGulag(ServerRequest $request, Response $response, array $args): ResponseInterface
+    {
+        $user = $this->user;
+        $passwd = $request->getParam('passwd');
+
+        if ($passwd === '') {
+            return ResponseHelper::error($response, '密码不能为空');
+        }
+
+        if (! Hash::checkPassword($user->pass, $passwd)) {
+            return ResponseHelper::error($response, '密码错误');
+        }
+
+        if ($_ENV['enable_kill']) {
+            Auth::logout();
+            $user->killUser();
+            return ResponseHelper::successfully($response, '你的帐号已被送去古拉格劳动改造,再见');
+        }
+
+        return ResponseHelper::error($response, '自助账号删除未启用');
+    }
+}

+ 3 - 3
src/Controllers/User/LogController.php

@@ -6,7 +6,7 @@ namespace App\Controllers\User;
 
 use App\Controllers\BaseController;
 use App\Models\DetectLog;
-use App\Models\UserSubscribeLog;
+use App\Models\SubscribeLog;
 use App\Utils\Tools;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
@@ -22,10 +22,10 @@ final class LogController extends BaseController
      */
     public function subscribe(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $logs = UserSubscribeLog::orderBy('id', 'desc')->where('user_id', $this->user->id)->get();
+        $logs = SubscribeLog::orderBy('id', 'desc')->where('user_id', $this->user->id)->get();
 
         foreach ($logs as $log) {
-            $log->location = Tools::getIpLocation($log->request_ip);
+            $log->request_time = Tools::toDateTime($log->request_time);
         }
 
         return $response->write($this->view()

+ 10 - 9
src/Controllers/User/TicketController.php

@@ -8,9 +8,11 @@ use App\Controllers\BaseController;
 use App\Models\Setting;
 use App\Models\Ticket;
 use App\Models\User;
+use App\Services\RateLimit;
 use App\Utils\Tools;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
+use RedisException;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 use voku\helper\AntiXSS;
@@ -49,23 +51,22 @@ final class TicketController extends BaseController
         );
     }
 
+    /**
+     * @throws RedisException
+     */
     public function ticketAdd(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
-        if (! Setting::obtain('enable_ticket')) {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => '暂时无法开启工单,请稍后再试',
-            ]);
-        }
-
         $title = $request->getParam('title') ?? '';
         $comment = $request->getParam('comment') ?? '';
         $type = $request->getParam('type') ?? '';
 
-        if ($this->user->is_shadow_banned) {
+        if (! Setting::obtain('enable_ticket') ||
+            $this->user->is_shadow_banned ||
+            ! RateLimit::checkTicketLimit($this->user->id)
+        ) {
             return $response->withJson([
                 'ret' => 0,
-                'msg' => '暂时无法开启工单,请稍后再试',
+                'msg' => '暂时无法开启工单,请稍后再试',
             ]);
         }
 

+ 2 - 307
src/Controllers/UserController.php

@@ -11,30 +11,17 @@ use App\Models\Node;
 use App\Models\OnlineLog;
 use App\Models\Payback;
 use App\Models\Setting;
-use App\Models\User;
 use App\Services\Auth;
-use App\Services\Cache;
 use App\Services\Captcha;
-use App\Services\Config;
-use App\Services\MFA;
-use App\Utils\Hash;
 use App\Utils\ResponseHelper;
-use App\Utils\Telegram;
 use App\Utils\Tools;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
-use Ramsey\Uuid\Uuid;
-use RedisException;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
-use voku\helper\AntiXSS;
-use function in_array;
 use function str_replace;
-use function strlen;
-use function strtolower;
 use function strtotime;
 use function time;
-use const BASE_PATH;
 
 /**
  *  HomeController
@@ -107,33 +94,13 @@ final class UserController extends BaseController
         );
     }
 
-    /**
-     * @throws Exception
-     */
-    public function edit(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $themes = Tools::getDir(BASE_PATH . '/resources/views');
-        $bind_token = Telegram::addBindSession($this->user);
-        $methods = Config::getSupportParam('method');
-        $gaurl = MFA::getGAurl($this->user);
-
-        return $response->write($this->view()
-            ->assign('user', $this->user)
-            ->assign('themes', $themes)
-            ->assign('bind_token', $bind_token)
-            ->assign('methods', $methods)
-            ->assign('gaurl', $gaurl)
-            ->assign('telegram_bot', $_ENV['telegram_bot'])
-            ->registerClass('Config', Config::class)
-            ->fetch('user/edit.tpl'));
-    }
-
     /**
      * @throws Exception
      */
     public function invite(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         $code = InviteCode::where('user_id', $this->user->id)->first();
+
         if ($code === null) {
             $this->user->addInviteCode();
             $code = InviteCode::where('user_id', $this->user->id)->first();
@@ -148,6 +115,7 @@ final class UserController extends BaseController
         }
 
         $paybacks_sum = Payback::where('ref_by', $this->user->id)->sum('ref_get');
+
         if (! $paybacks_sum) {
             $paybacks_sum = 0;
         }
@@ -162,232 +130,6 @@ final class UserController extends BaseController
             ->fetch('user/invite.tpl'));
     }
 
-    public function updatePassword(ServerRequest $request, Response $response, array $args): ResponseInterface
-    {
-        $oldpwd = $request->getParam('oldpwd');
-        $pwd = $request->getParam('pwd');
-        $repwd = $request->getParam('repwd');
-        $user = $this->user;
-
-        if ($oldpwd === '' || $pwd === '' || $repwd === '') {
-            return ResponseHelper::error($response, '密码不能为空');
-        }
-
-        if (! Hash::checkPassword($user->pass, $oldpwd)) {
-            return ResponseHelper::error($response, '旧密码错误');
-        }
-
-        if ($pwd !== $repwd) {
-            return ResponseHelper::error($response, '两次输入不符合');
-        }
-
-        if (strlen($pwd) < 8) {
-            return ResponseHelper::error($response, '密码太短啦');
-        }
-
-        $hashPwd = Hash::passwordHash($pwd);
-        $user->pass = $hashPwd;
-
-        if (! $user->save()) {
-            return ResponseHelper::error($response, '修改失败');
-        }
-
-        if (Setting::obtain('enable_forced_replacement')) {
-            $user->cleanLink();
-        }
-
-        return ResponseHelper::successfully($response, '修改成功');
-    }
-
-    /**
-     * @throws RedisException
-     */
-    public function updateEmail(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $antiXss = new AntiXSS();
-        $user = $this->user;
-        $new_email = $antiXss->xss_clean($request->getParam('newemail'));
-        $old_email = $user->email;
-
-        if (! $_ENV['enable_change_email']) {
-            return ResponseHelper::error($response, '此项不允许自行修改,请联系管理员操作');
-        }
-
-        if ($new_email === '') {
-            return ResponseHelper::error($response, '未填写邮箱');
-        }
-
-        if (! Tools::isEmailLegal($new_email)) {
-            return $response->withJson($check_res);
-        }
-
-        $exist_user = User::where('email', $new_email)->first();
-
-        if ($exist_user !== null) {
-            return ResponseHelper::error($response, '邮箱已经被使用了');
-        }
-
-        if ($new_email === $old_email) {
-            return ResponseHelper::error($response, '新邮箱不能和旧邮箱一样');
-        }
-
-        if (Setting::obtain('reg_email_verify')) {
-            $redis = Cache::initRedis();
-            $email_verify_code = $request->getParam('emailcode');
-            $email_verify = $redis->get($email_verify_code);
-
-            if (! $email_verify) {
-                return ResponseHelper::error($response, '你的邮箱验证码不正确');
-            }
-
-            $redis->del($email_verify_code);
-        }
-
-        $user->email = $new_email;
-
-        if (! $user->save()) {
-            return ResponseHelper::error($response, '修改失败');
-        }
-
-        return ResponseHelper::successfully($response, '修改成功');
-    }
-
-    public function updateUsername(ServerRequest $request, Response $response, array $args): ResponseInterface
-    {
-        $antiXss = new AntiXSS();
-        $newusername = $antiXss->xss_clean($request->getParam('newusername'));
-        $user = $this->user;
-        $user->user_name = $newusername;
-
-        if (! $user->save()) {
-            return ResponseHelper::error($response, '修改失败');
-        }
-
-        return ResponseHelper::successfully($response, '修改成功');
-    }
-
-    public function updateContact(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $antiXss = new AntiXSS();
-        $type = $antiXss->xss_clean($request->getParam('imtype'));
-        $value = $antiXss->xss_clean($request->getParam('imvalue'));
-        $user = $this->user;
-
-        if ($user->telegram_id !== null) {
-            return ResponseHelper::error($response, '你的账户绑定了 Telegram ,所以此项并不能被修改');
-        }
-
-        if ($value === '' || $type === '') {
-            return ResponseHelper::error($response, '联络方式不能为空');
-        }
-
-        if (User::where('im_value', $value)->where('im_type', $type)->first() !== null) {
-            return ResponseHelper::error($response, '此联络方式已经被注册');
-        }
-
-        $user->im_type = $type;
-        $user->im_value = $value;
-
-        if (! $user->save()) {
-            return ResponseHelper::error($response, '修改失败');
-        }
-
-        return ResponseHelper::successfully($response, '修改成功');
-    }
-
-    public function updateTheme(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $antiXss = new AntiXSS();
-        $theme = $antiXss->xss_clean($request->getParam('theme'));
-        $user = $this->user;
-
-        if ($theme === '') {
-            return ResponseHelper::error($response, '主题不能为空');
-        }
-
-        $user->theme = $theme;
-
-        if (! $user->save()) {
-            return ResponseHelper::error($response, '修改失败');
-        }
-
-        return ResponseHelper::successfully($response, '修改成功');
-    }
-
-    public function updateMail(ServerRequest $request, Response $response, array $args): ResponseInterface
-    {
-        $value = (int) $request->getParam('mail');
-
-        if (! in_array($value, [0, 1, 2])) {
-            return ResponseHelper::error($response, '参数错误');
-        }
-
-        $user = $this->user;
-
-        if ($value === 2 && ! $_ENV['enable_telegram']) {
-            return ResponseHelper::error(
-                $response,
-                '修改失败,当前无法使用 Telegram 接收每日报告'
-            );
-        }
-
-        $user->daily_mail_enable = $value;
-
-        if (! $user->save()) {
-            return ResponseHelper::error($response, '修改失败');
-        }
-
-        return ResponseHelper::successfully($response, '修改成功');
-    }
-
-    public function resetPasswd(ServerRequest $request, Response $response, array $args): ResponseInterface
-    {
-        $user = $this->user;
-        $user->uuid = Uuid::uuid4();
-        $user->passwd = Tools::genRandomChar(16);
-
-        if (! $user->save()) {
-            return ResponseHelper::error($response, '修改失败');
-        }
-
-        return ResponseHelper::successfully($response, '修改成功');
-    }
-
-    public function resetApiToken(ServerRequest $request, Response $response, array $args): ResponseInterface
-    {
-        $user = $this->user;
-        $user->api_token = Uuid::uuid4();
-
-        if (! $user->save()) {
-            return ResponseHelper::error($response, '修改失败');
-        }
-
-        return ResponseHelper::successfully($response, '修改成功');
-    }
-
-    public function updateMethod(ServerRequest $request, Response $response, array $args): ResponseInterface
-    {
-        $antiXss = new AntiXSS();
-
-        $user = $this->user;
-        $method = strtolower($antiXss->xss_clean($request->getParam('method')));
-
-        if ($method === '') {
-            ResponseHelper::error($response, '非法输入');
-        }
-        if (! Tools::isParamValidate('method', $method)) {
-            ResponseHelper::error($response, '加密无效');
-        }
-
-        $user->method = $method;
-
-        if (! $user->save()) {
-            return ResponseHelper::error($response, '修改失败');
-        }
-
-        return ResponseHelper::successfully($response, '修改成功');
-    }
-
     public function logout(ServerRequest $request, Response $response, array $args): Response
     {
         Auth::logout();
@@ -430,36 +172,6 @@ final class UserController extends BaseController
         ]);
     }
 
-    /**
-     * @throws Exception
-     */
-    public function kill(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        return $response->write($this->view()->fetch('user/kill.tpl'));
-    }
-
-    public function handleKill(ServerRequest $request, Response $response, array $args): ResponseInterface
-    {
-        $user = $this->user;
-        $passwd = $request->getParam('passwd');
-
-        if ($passwd === '') {
-            return ResponseHelper::error($response, '密码不能为空');
-        }
-
-        if (! Hash::checkPassword($user->pass, $passwd)) {
-            return ResponseHelper::error($response, '密码错误');
-        }
-
-        if ($_ENV['enable_kill']) {
-            Auth::logout();
-            $user->killUser();
-            return ResponseHelper::successfully($response, '你的帐号已经从我们的系统中删除。欢迎下次光临');
-        }
-
-        return ResponseHelper::error($response, '自助账号删除未启用,如需删除账户请联系管理员。');
-    }
-
     /**
      * @throws Exception
      */
@@ -480,26 +192,9 @@ final class UserController extends BaseController
         return ResponseHelper::successfully($response, '重置成功');
     }
 
-    public function resetURL(ServerRequest $request, Response $response, array $args): ResponseInterface
-    {
-        $user = $this->user;
-        $user->cleanLink();
-
-        return ResponseHelper::successfully($response, '重置成功');
-    }
-
-    public function resetInviteURL(ServerRequest $request, Response $response, array $args): ResponseInterface
-    {
-        $user = $this->user;
-        $user->clearInviteCodes();
-
-        return ResponseHelper::successfully($response, '重置成功');
-    }
-
     public function switchThemeMode(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         $user = $this->user;
-
         $user->is_dark_mode = $user->is_dark_mode === 1 ? 0 : 1;
 
         if (! $user->save()) {

+ 52 - 0
src/Models/SubscribeLog.php

@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Models;
+
+use App\Utils\Tools;
+use GeoIp2\Exception\AddressNotFoundException;
+use MaxMind\Db\Reader\InvalidDatabaseException;
+use voku\helper\AntiXSS;
+use function time;
+
+final class SubscribeLog extends Model
+{
+    protected $connection = 'default';
+    protected $table = 'subscribe_log';
+
+    /**
+     * 用户
+     */
+    public function user(): ?User
+    {
+        return User::find($this->user_id);
+    }
+
+    /**
+     * Ip 地理位置
+     */
+    public function getLocationAttribute(): string
+    {
+        try {
+            return Tools::getIpLocation($this->request_ip);
+        } catch (AddressNotFoundException | InvalidDatabaseException $e) {
+            return '未知';
+        }
+    }
+
+    /**
+     * 记录订阅日志
+     */
+    public static function add(User $user, string $type, string $ua): void
+    {
+        $log = new SubscribeLog();
+        $antiXss = new AntiXSS();
+        $log->user_id = $user->id;
+        $log->type = $antiXss->xss_clean($type);
+        $log->request_ip = $_SERVER['REMOTE_ADDR'];
+        $log->request_user_agent = $antiXss->xss_clean($ua);
+        $log->request_time = time();
+        $log->save();
+    }
+}

+ 1 - 1
src/Models/User.php

@@ -387,7 +387,7 @@ final class User extends Model
         OnlineLog::where('user_id', '=', $uid)->delete();
         Link::where('userid', '=', $uid)->delete();
         LoginIp::where('userid', '=', $uid)->delete();
-        UserSubscribeLog::where('user_id', '=', $uid)->delete();
+        SubscribeLog::where('user_id', '=', $uid)->delete();
 
         $this->delete();
 

+ 0 - 42
src/Models/UserSubscribeLog.php

@@ -1,42 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Models;
-
-use voku\helper\AntiXSS;
-
-final class UserSubscribeLog extends Model
-{
-    protected $connection = 'default';
-    protected $table = 'user_subscribe_log';
-
-    /**
-     * 用户
-     */
-    public function user(): ?User
-    {
-        return User::find($this->user_id);
-    }
-
-    /**
-     * 记录订阅日志
-     *
-     * @param User   $user 用户
-     * @param string $type 订阅类型
-     * @param string $ua   UA
-     */
-    public static function addSubscribeLog(User $user, string $type, string $ua): void
-    {
-        $log = new UserSubscribeLog();
-        $antiXss = new AntiXSS();
-        $log->user_name = $user->user_name;
-        $log->user_id = $user->id;
-        $log->email = $user->email;
-        $log->subscribe_type = $antiXss->xss_clean($type);
-        $log->request_ip = $_SERVER['REMOTE_ADDR'];
-        $log->request_time = date('Y-m-d H:i:s');
-        $log->request_user_agent = $antiXss->xss_clean($ua);
-        $log->save();
-    }
-}

+ 5 - 6
src/Services/CronDetect.php

@@ -10,8 +10,8 @@ use App\Models\Node;
 use App\Models\Setting;
 use App\Models\User;
 use App\Utils\Telegram;
+use App\Utils\Tools;
 use Telegram\Bot\Exceptions\TelegramSDKException;
-use function date;
 use function file_get_contents;
 use function in_array;
 use function json_decode;
@@ -139,10 +139,9 @@ final class CronDetect
 
             if ($detect_number >= $_ENV['auto_detect_ban_number']) {
                 $last_detect_ban_time = $user->last_detect_ban_time;
-                $end_time = date('Y-m-d H:i:s');
                 $user->is_banned = 1;
                 $user->banned_reason = 'DetectBan';
-                $user->last_detect_ban_time = $end_time;
+                $user->last_detect_ban_time = Tools::toDateTime(time());
                 $user->save();
                 $DetectBanLog = new DetectBanLog();
                 $DetectBanLog->user_name = $user->user_name;
@@ -151,13 +150,13 @@ final class CronDetect
                 $DetectBanLog->detect_number = $detect_number;
                 $DetectBanLog->ban_time = $_ENV['auto_detect_ban_time'];
                 $DetectBanLog->start_time = strtotime($last_detect_ban_time);
-                $DetectBanLog->end_time = strtotime($end_time);
+                $DetectBanLog->end_time = time();
                 $DetectBanLog->all_detect_number = $user->all_detect_number;
                 $DetectBanLog->save();
             }
         }
 
-        echo date('Y-m-d H:i:s') . ' 审计封禁检查结束' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 审计封禁检查结束' . PHP_EOL;
 
         // 审计封禁解封
         $banned_users = User::where('is_banned', 1)->where('banned_reason', 'DetectBan')->get();
@@ -171,6 +170,6 @@ final class CronDetect
             }
         }
 
-        echo date('Y-m-d H:i:s') . ' 审计解封检查结束' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 审计解封检查结束' . PHP_EOL;
     }
 }

+ 27 - 26
src/Services/CronJob.php

@@ -13,10 +13,9 @@ use App\Models\OnlineLog;
 use App\Models\Order;
 use App\Models\Paylist;
 use App\Models\Setting;
-use App\Models\StreamMedia;
+use App\Models\SubscribeLog;
 use App\Models\User;
 use App\Models\UserHourlyUsage;
-use App\Models\UserSubscribeLog;
 use App\Utils\Telegram;
 use App\Utils\Tools;
 use DateTime;
@@ -55,22 +54,23 @@ final class CronJob
             $trafficlog->datetime = time();
             $trafficlog->save();
         }
+
+        echo Tools::toDateTime(time()) . ' 流量记录处理完成' . PHP_EOL;
     }
 
     public static function cleanDb(): void
     {
-        UserSubscribeLog::where(
+        SubscribeLog::where(
             'request_time',
             '<',
-            date('Y-m-d H:i:s', time() - 86400 * (int) $_ENV['subscribeLog_keep_days'])
+            time() - 86400 * (int) $_ENV['subscribeLog_keep_days']
         )->delete();
         UserHourlyUsage::where('datetime', '<', time() - 86400 * (int) $_ENV['trafficLog_keep_days'])->delete();
         DetectLog::where('datetime', '<', time() - 86400 * 3)->delete();
         EmailQueue::where('time', '<', time() - 86400)->delete();
         OnlineLog::where('last_time', '<', time() - 86400)->delete();
-        StreamMedia::where('created_at', '<', time() - 86400)->delete();
 
-        echo date('Y-m-d H:i:s') . ' 数据库清理完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 数据库清理完成' . PHP_EOL;
     }
 
     public static function detectInactiveUser(): void
@@ -93,7 +93,8 @@ final class CronJob
             ->where('last_use_time', '>', time() - 86400 * $use_days)
             ->update(['is_inactive' => 0]);
 
-        echo date('Y-m-d H:i:s') . ' 检测到 ' . User::where('is_inactive', '=', '1')->count() . ' 个账户处于闲置状态' . PHP_EOL;
+        echo Tools::toDateTime(time()) .
+            ' 检测到 ' . User::where('is_inactive', '=', '1')->count() . ' 个账户处于闲置状态' . PHP_EOL;
     }
 
     /**
@@ -165,7 +166,7 @@ final class CronJob
                 $node->save();
             }
         }
-        echo date('Y-m-d H:i:s') . ' 节点离线检测完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 节点离线检测完成' . PHP_EOL;
     }
 
     public static function expirePaidUserAccount(): void
@@ -200,7 +201,7 @@ final class CronJob
             }
         }
 
-        echo date('Y-m-d H:i:s') . ' 付费用户过期检测完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 付费用户过期检测完成' . PHP_EOL;
     }
 
     public static function processEmailQueue(): void
@@ -210,7 +211,7 @@ final class CronJob
         //邮件队列处理
         while (true) {
             if (time() - $timestamp > 299) {
-                echo date('Y-m-d H:i:s') . '邮件队列处理超时,已跳过' . PHP_EOL;
+                echo Tools::toDateTime(time()) . '邮件队列处理超时,已跳过' . PHP_EOL;
                 break;
             }
             DB::beginTransaction();
@@ -242,7 +243,7 @@ final class CronJob
             DB::commit();
         }
 
-        echo date('Y-m-d H:i:s') . ' 邮件队列处理完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 邮件队列处理完成' . PHP_EOL;
     }
 
     public static function processTabpOrderActivation(): void
@@ -303,7 +304,7 @@ final class CronJob
             }
         }
 
-        echo date('Y-m-d H:i:s') . ' TABP订单激活处理完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' TABP订单激活处理完成' . PHP_EOL;
     }
 
     public static function processBandwidthOrderActivation(): void
@@ -332,7 +333,7 @@ final class CronJob
             }
         }
 
-        echo date('Y-m-d H:i:s') . ' 流量包订单激活处理完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 流量包订单激活处理完成' . PHP_EOL;
     }
 
     /**
@@ -376,7 +377,7 @@ final class CronJob
             }
         }
 
-        echo date('Y-m-d H:i:s') . ' 时间包订单激活处理完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 时间包订单激活处理完成' . PHP_EOL;
     }
 
     public static function processPendingOrder(): void
@@ -411,21 +412,21 @@ final class CronJob
             }
         }
 
-        echo date('Y-m-d H:i:s') . ' 等待中订单处理完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 等待中订单处理完成' . PHP_EOL;
     }
 
     public static function resetNodeBandwidth(): void
     {
         Node::where('bandwidthlimit_resetday', date('d'))->update(['node_bandwidth' => 0]);
 
-        echo date('Y-m-d H:i:s') . ' 重设节点流量完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 重设节点流量完成' . PHP_EOL;
     }
 
     public static function resetTodayTraffic(): void
     {
         User::query()->update(['transfer_today' => 0]);
 
-        echo date('Y-m-d H:i:s') . ' 重设用户每日流量完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 重设用户每日流量完成' . PHP_EOL;
     }
 
     public static function resetFreeUserTraffic(): void
@@ -449,7 +450,7 @@ final class CronJob
             );
         }
 
-        echo date('Y-m-d H:i:s') . ' 重设免费用户流量完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 重设免费用户流量完成' . PHP_EOL;
     }
 
     public static function sendDailyFinanceMail(): void
@@ -484,7 +485,7 @@ final class CronJob
             );
         }
 
-        echo date('Y-m-d H:i:s') . ' 成功发送财务日报' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 成功发送财务日报' . PHP_EOL;
     }
 
     public static function sendWeeklyFinanceMail(): void
@@ -508,7 +509,7 @@ final class CronJob
             );
         }
 
-        echo date('Y-m-d H:i:s') . ' 成功发送财务周报' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 成功发送财务周报' . PHP_EOL;
     }
 
     public static function sendMonthlyFinanceMail(): void
@@ -532,7 +533,7 @@ final class CronJob
             );
         }
 
-        echo date('Y-m-d H:i:s') . ' 成功发送财务月报' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 成功发送财务月报' . PHP_EOL;
     }
 
     public static function sendPaidUserUsageLimitNotification(): void
@@ -578,7 +579,7 @@ final class CronJob
             }
         }
 
-        echo date('Y-m-d H:i:s') . ' 付费用户用量限制提醒完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 付费用户用量限制提醒完成' . PHP_EOL;
     }
 
     public static function sendDailyTrafficReport(): void
@@ -597,7 +598,7 @@ final class CronJob
             $user->sendDailyNotification($ann_latest);
         }
 
-        echo date('Y-m-d H:i:s') . ' 成功发送每日邮件' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 成功发送每日邮件' . PHP_EOL;
     }
 
     /**
@@ -607,7 +608,7 @@ final class CronJob
     {
         Telegram::send(Setting::obtain('telegram_daily_job_text'));
 
-        echo date('Y-m-d H:i:s') . ' 成功发送 Telegram 每日任务提示' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 成功发送 Telegram 每日任务提示' . PHP_EOL;
     }
 
     /**
@@ -631,7 +632,7 @@ final class CronJob
             )
         );
 
-        echo date('Y-m-d H:i:s') . ' 成功发送 Telegram 系统运行日志' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 成功发送 Telegram 系统运行日志' . PHP_EOL;
     }
 
     public static function updateNodeIp(): void
@@ -646,6 +647,6 @@ final class CronJob
             }
         }
 
-        echo date('Y-m-d H:i:s') . ' 更新节点 IP 完成' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 更新节点 IP 完成' . PHP_EOL;
     }
 }

+ 1 - 1
src/Services/Password.php

@@ -20,7 +20,7 @@ final class Password
         $redis = Cache::initRedis();
         $token = Tools::genRandomChar(64);
 
-        $redis->setex($token, Setting::obtain('email_password_reset_ttl'), $email);
+        $redis->setex('password_reset:' . $token, Setting::obtain('email_password_reset_ttl'), $email);
 
         $subject = $_ENV['appName'] . '-重置密码';
         $resetUrl = $_ENV['baseUrl'] . '/password/token/' . $token;

+ 19 - 0
src/Services/RateLimit.php

@@ -144,4 +144,23 @@ final class RateLimit
 
         return true;
     }
+
+    /**
+     * @throws RedisException
+     */
+    public static function checkTicketLimit(string $user_id): bool
+    {
+        $ticket_limiter = new RedisRateLimiter(
+            Rate::custom(Setting::obtain('ticket_limit'), 2592000),
+            Cache::initRedis()
+        );
+
+        try {
+            $ticket_limiter->limit($user_id);
+        } catch (LimitExceeded $e) {
+            return false;
+        }
+
+        return true;
+    }
 }

+ 2 - 2
src/Utils/Telegram.php

@@ -163,7 +163,7 @@ final class Telegram
     {
         $antiXss = new AntiXSS();
         $redis = Cache::initRedis();
-        $uid = $redis->get($antiXss->xss_clean($token));
+        $uid = $redis->get('telegram_bind:' . $antiXss->xss_clean($token));
 
         if (! $uid) {
             return 0;
@@ -183,7 +183,7 @@ final class Telegram
         $token = self::generateRandomLink();
 
         $redis->setex(
-            $token,
+            'telegram_bind:' . $token,
             600,
             $user->id
         );

+ 27 - 12
src/Utils/Telegram/Callback.php

@@ -11,7 +11,7 @@ use App\Models\LoginIp;
 use App\Models\OnlineLog;
 use App\Models\Payback;
 use App\Models\Setting;
-use App\Models\UserSubscribeLog;
+use App\Models\SubscribeLog;
 use App\Services\Config;
 use App\Utils\Tools;
 use GeoIp2\Exception\AddressNotFoundException;
@@ -411,7 +411,11 @@ final class Callback
         switch ($OpEnd) {
             case 'login_log':
                 // 登录记录
-                $total = LoginIp::where('userid', '=', $this->User->id)->where('type', '=', 0)->orderBy('datetime', 'desc')->take(10)->get();
+                $total = LoginIp::where('userid', '=', $this->User->id)
+                    ->where('type', '=', 0)
+                    ->orderBy('datetime', 'desc')
+                    ->take(10)
+                    ->get();
                 $text = '<strong>以下是你最近 10 次的登录 IP 和地理位置:</strong>' . PHP_EOL;
                 $text .= PHP_EOL;
 
@@ -466,7 +470,9 @@ final class Callback
                 $paybacks = Payback::where('ref_by', $this->User->id)->orderBy('datetime', 'desc')->take(10)->get();
                 $temp = [];
                 foreach ($paybacks as $payback) {
-                    $temp[] = '<code>#' . $payback->id . ':' . ($payback->user() !== null ? $payback->user()->user_name : '已注销') . ':' . $payback->ref_get . ' 元</code>';
+                    $temp[] = '<code>#' . $payback->id .
+                        ':' . ($payback->user() !== null ? $payback->user()->user_name : '已注销') . ':' .
+                        $payback->ref_get . ' 元</code>';
                 }
                 $text = '<strong>以下是你最近 10 次返利记录:</strong>';
                 $text .= PHP_EOL . PHP_EOL;
@@ -485,11 +491,13 @@ final class Callback
                 break;
             case 'subscribe_log':
                 // 订阅记录
-                $logs = UserSubscribeLog::orderBy('id', 'desc')->where('user_id', $this->User->id)->take(10)->get();
+                $logs = SubscribeLog::orderBy('id', 'desc')->where('user_id', $this->User->id)->take(10)->get();
                 $temp = [];
                 foreach ($logs as $log) {
                     $location = Tools::getIpLocation($log->request_ip);
-                    $temp[] = '<code>' . $log->request_time . ' 在 [' . $log->request_ip . '] ' . $location . ' 访问了 ' . $log->subscribe_type . ' 订阅</code>';
+                    $temp[] = '<code>' . Tools::toDateTime($log->request_time) .
+                        ' 在 [' . $log->request_ip . '] ' . $location .
+                        ' 访问了 ' . $log->type . ' 订阅</code>';
                 }
                 $text = '<strong>以下是你最近 10 次订阅记录:</strong>';
                 $text .= PHP_EOL . PHP_EOL;
@@ -888,13 +896,20 @@ final class Callback
             $UniversalSub_Url = SubController::getUniversalSub($this->User);
             $TraditionalSub_Url = LinkController::getTraditionalSub($this->User);
             $text = match ($CallbackDataExplode[1]) {
-                'clash' => 'Clash 通用订阅地址:' . PHP_EOL . PHP_EOL . '<code>' . $UniversalSub_Url . '/clash</code>' . PHP_EOL . PHP_EOL,
-                'json' => 'Json 通用订阅地址:' . PHP_EOL . PHP_EOL . '<code>' . $UniversalSub_Url . '/json</code>' . PHP_EOL . PHP_EOL,
-                'sip008' => 'Shadowsocks SIP008 通用订阅地址:' . PHP_EOL . PHP_EOL . '<code>' . $UniversalSub_Url . '/sip008</code>' . PHP_EOL . PHP_EOL,
-                'ss' => 'Shadowsocks 传统订阅地址:' . PHP_EOL . PHP_EOL . '<code>' . $TraditionalSub_Url . '?ss=1</code>' . PHP_EOL . PHP_EOL,
-                'sip002' => 'Shadowsocks SIP002 传统订阅地址:' . PHP_EOL . PHP_EOL . '<code>' . $TraditionalSub_Url . '?sip002=1</code>' . PHP_EOL . PHP_EOL,
-                'v2' => 'V2Ray 传统订阅地址:' . PHP_EOL . PHP_EOL . '<code>' . $TraditionalSub_Url . '?v2ray=1</code>' . PHP_EOL . PHP_EOL,
-                'trojan' => 'Trojan 传统订阅地址:' . PHP_EOL . PHP_EOL . '<code>' . $TraditionalSub_Url . '?trojan=1</code>' . PHP_EOL . PHP_EOL,
+                'clash' => 'Clash 通用订阅地址:' . PHP_EOL . PHP_EOL .
+                    '<code>' . $UniversalSub_Url . '/clash</code>' . PHP_EOL . PHP_EOL,
+                'json' => 'Json 通用订阅地址:' . PHP_EOL . PHP_EOL .
+                    '<code>' . $UniversalSub_Url . '/json</code>' . PHP_EOL . PHP_EOL,
+                'sip008' => 'Shadowsocks SIP008 通用订阅地址:' . PHP_EOL . PHP_EOL .
+                    '<code>' . $UniversalSub_Url . '/sip008</code>' . PHP_EOL . PHP_EOL,
+                'ss' => 'Shadowsocks 传统订阅地址:' . PHP_EOL . PHP_EOL .
+                    '<code>' . $TraditionalSub_Url . '?ss=1</code>' . PHP_EOL . PHP_EOL,
+                'sip002' => 'Shadowsocks SIP002 传统订阅地址:' . PHP_EOL . PHP_EOL .
+                    '<code>' . $TraditionalSub_Url . '?sip002=1</code>' . PHP_EOL . PHP_EOL,
+                'v2' => 'V2Ray 传统订阅地址:' . PHP_EOL . PHP_EOL .
+                    '<code>' . $TraditionalSub_Url . '?v2ray=1</code>' . PHP_EOL . PHP_EOL,
+                'trojan' => 'Trojan 传统订阅地址:' . PHP_EOL . PHP_EOL .
+                    '<code>' . $TraditionalSub_Url . '?trojan=1</code>' . PHP_EOL . PHP_EOL,
                 default => '未知参数' . PHP_EOL . PHP_EOL,
             };
             $sendMessage = [