Browse Source

Merge pull request #2386 from SSPanel-UIM/dev

Dev 20240304
M1Screw 1 year ago
parent
commit
15ec3b287f
44 changed files with 466 additions and 415 deletions
  1. 0 2
      README.md
  2. 1 1
      app/routes.php
  3. 1 0
      composer.json
  4. 208 146
      composer.lock
  5. 0 1
      config/.config.example.php
  6. 30 0
      config/settings.json
  7. 2 1
      db/migrations/2023020100-init.php
  8. 0 1
      db/migrations/2023061800-update_new_shop_data_type.php
  9. 30 0
      db/migrations/2024030300-update_node_ip.php
  10. 8 2
      resources/views/tabler/admin/node/edit.tpl
  11. 61 31
      resources/views/tabler/admin/setting/email.tpl
  12. 7 5
      resources/views/tabler/user/index.tpl
  13. 2 4
      resources/views/tabler/user/ticket/index.tpl
  14. 0 13
      src/Command/ClientDownload.php
  15. 2 2
      src/Command/Migration.php
  16. 0 4
      src/Controllers/Admin/AnnController.php
  17. 0 4
      src/Controllers/Admin/DocsController.php
  18. 4 0
      src/Controllers/Admin/Setting/EmailController.php
  19. 0 4
      src/Controllers/Admin/UserController.php
  20. 0 19
      src/Controllers/AuthController.php
  21. 0 4
      src/Controllers/SubController.php
  22. 6 0
      src/Controllers/WebAPI/FuncController.php
  23. 3 0
      src/Controllers/WebAPI/NodeController.php
  24. 0 16
      src/Controllers/WebAPI/UserController.php
  25. 3 1
      src/Middleware/NodeToken.php
  26. 13 6
      src/Models/Node.php
  27. 0 3
      src/Models/User.php
  28. 0 2
      src/Services/Analytics.php
  29. 3 3
      src/Services/Auth/Cookie.php
  30. 8 3
      src/Services/DB.php
  31. 3 2
      src/Services/Detect.php
  32. 8 7
      src/Services/Mail.php
  33. 1 1
      src/Services/Mail/Base.php
  34. 38 0
      src/Services/Mail/Mailchimp.php
  35. 4 14
      src/Services/Mail/Mailgun.php
  36. 1 1
      src/Services/Mail/NullMail.php
  37. 3 11
      src/Services/Mail/Postal.php
  38. 4 20
      src/Services/Mail/SendGrid.php
  39. 2 6
      src/Services/Mail/Ses.php
  40. 10 20
      src/Services/Mail/Smtp.php
  41. 0 4
      src/Services/Subscribe.php
  42. 0 12
      src/Utils/ResponseHelper.php
  43. 0 38
      src/Utils/Tools.php
  44. 0 1
      tests/App/Services/DBTest.php

+ 0 - 2
README.md

@@ -40,8 +40,6 @@ SSPanel UIM is a multi-purpose proxy service sales management system designed fo
 - 深度集成大型语言模型(Large Language Model),支持工单智能回复,文档生成等功能
 - 一键对接 OpenAI,Gemini Pro,Hugging Face Hosted API 和 Cloudflare Workers AI 等人工智能服务
 
-
-
 - Integrate multiple payment systems such as Alipay F2F, PayPal, Stripe, etc.
 - Support multiple mail services, built-in mail queue function, no third-party components are required to use
 - Built-in tabler theme based on Bootstrap 5, template engine support

+ 1 - 1
app/routes.php

@@ -318,7 +318,7 @@ return static function (Slim\App $app): void {
 
     // WebAPI V2(Aka Node API V1)
     //$app->group('/node/api/v1', function (RouteCollectorProxy $group): void {
-    //    $group->get('/heartbeat', App\Controllers\Api\NodeApiV1Controller::class . ':getHeartbeat');
+    //    $group->put('/heartbeat', App\Controllers\Api\NodeApiV1Controller::class . ':getHeartbeat');
     //    $group->get('/info', App\Controllers\Api\NodeApiV1Controller::class . ':getInfo');
     //    $group->get('/user', App\Controllers\Api\NodeApiV1Controller::class . ':getUser');
     //    $group->get('/detect_rule', App\Controllers\Api\NodeApiV1Controller::class . ':getDetectRule');

+ 1 - 0
composer.json

@@ -23,6 +23,7 @@
         "illuminate/pagination": "^10.44.0",
         "irazasyed/telegram-bot-sdk": "^3",
         "lcobucci/jwt": "^5",
+        "mailchimp/transactional": "^1.0",
         "mailgun/mailgun-php": "^4",
         "nikolaposa/rate-limit": "^3",
         "openai-php/client": "^0",

+ 208 - 146
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "f7e6edab9c07262546075ea15e3c323c",
+    "content-hash": "03de1f31b0fae31274317471f8bb4e16",
     "packages": [
         {
             "name": "alipaysdk/openapi",
@@ -124,16 +124,16 @@
         },
         {
             "name": "aws/aws-sdk-php",
-            "version": "3.300.4",
+            "version": "3.300.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/aws/aws-sdk-php.git",
-                "reference": "27d59c22c121ce9c0041c563dc9d7270e180925c"
+                "reference": "2704b9b10b42d53066eb383f47541124296db77c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/27d59c22c121ce9c0041c563dc9d7270e180925c",
-                "reference": "27d59c22c121ce9c0041c563dc9d7270e180925c",
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2704b9b10b42d53066eb383f47541124296db77c",
+                "reference": "2704b9b10b42d53066eb383f47541124296db77c",
                 "shasum": ""
             },
             "require": {
@@ -213,9 +213,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.300.4"
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.300.9"
             },
-            "time": "2024-02-23T19:10:30+00:00"
+            "time": "2024-03-01T19:04:32+00:00"
         },
         {
             "name": "bacon/bacon-qr-code",
@@ -1201,16 +1201,16 @@
         },
         {
             "name": "illuminate/collections",
-            "version": "v10.45.1",
+            "version": "v10.46.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/collections.git",
-                "reference": "04c24117515ab9b701e4b28506d3f3c15d14bd5f"
+                "reference": "dd0c652dfac0901c17bcfac94fe792e615b56e12"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/illuminate/collections/zipball/04c24117515ab9b701e4b28506d3f3c15d14bd5f",
-                "reference": "04c24117515ab9b701e4b28506d3f3c15d14bd5f",
+                "url": "https://api.github.com/repos/illuminate/collections/zipball/dd0c652dfac0901c17bcfac94fe792e615b56e12",
+                "reference": "dd0c652dfac0901c17bcfac94fe792e615b56e12",
                 "shasum": ""
             },
             "require": {
@@ -1252,11 +1252,11 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2024-02-13T21:55:58+00:00"
+            "time": "2024-02-21T14:18:14+00:00"
         },
         {
             "name": "illuminate/conditionable",
-            "version": "v10.45.1",
+            "version": "v10.46.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/conditionable.git",
@@ -1302,7 +1302,7 @@
         },
         {
             "name": "illuminate/container",
-            "version": "v10.45.1",
+            "version": "v10.46.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/container.git",
@@ -1353,7 +1353,7 @@
         },
         {
             "name": "illuminate/contracts",
-            "version": "v10.45.1",
+            "version": "v10.46.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/contracts.git",
@@ -1401,16 +1401,16 @@
         },
         {
             "name": "illuminate/database",
-            "version": "v10.45.1",
+            "version": "v10.46.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/database.git",
-                "reference": "64394348243be403ac9edf21c400077304e005b2"
+                "reference": "d756278a38541ec2f25c75e2553398b37a6d4e42"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/illuminate/database/zipball/64394348243be403ac9edf21c400077304e005b2",
-                "reference": "64394348243be403ac9edf21c400077304e005b2",
+                "url": "https://api.github.com/repos/illuminate/database/zipball/d756278a38541ec2f25c75e2553398b37a6d4e42",
+                "reference": "d756278a38541ec2f25c75e2553398b37a6d4e42",
                 "shasum": ""
             },
             "require": {
@@ -1470,11 +1470,11 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2024-02-20T15:20:15+00:00"
+            "time": "2024-02-26T16:14:36+00:00"
         },
         {
             "name": "illuminate/macroable",
-            "version": "v10.45.1",
+            "version": "v10.46.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/macroable.git",
@@ -1520,7 +1520,7 @@
         },
         {
             "name": "illuminate/pagination",
-            "version": "v10.45.1",
+            "version": "v10.46.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/pagination.git",
@@ -1570,16 +1570,16 @@
         },
         {
             "name": "illuminate/support",
-            "version": "v10.45.1",
+            "version": "v10.46.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/support.git",
-                "reference": "ef80b6c0d0faec648d0625f1bd2b604b61cba5e5"
+                "reference": "96d4512df39bee8cb60d50783f944a48242ea862"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/illuminate/support/zipball/ef80b6c0d0faec648d0625f1bd2b604b61cba5e5",
-                "reference": "ef80b6c0d0faec648d0625f1bd2b604b61cba5e5",
+                "url": "https://api.github.com/repos/illuminate/support/zipball/96d4512df39bee8cb60d50783f944a48242ea862",
+                "reference": "96d4512df39bee8cb60d50783f944a48242ea862",
                 "shasum": ""
             },
             "require": {
@@ -1637,7 +1637,7 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2024-02-16T10:04:27+00:00"
+            "time": "2024-02-26T22:20:06+00:00"
         },
         {
             "name": "irazasyed/telegram-bot-sdk",
@@ -1913,6 +1913,60 @@
             },
             "time": "2022-10-29T09:31:25+00:00"
         },
+        {
+            "name": "mailchimp/transactional",
+            "version": "1.0.59",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mailchimp/mailchimp-transactional-php.git",
+                "reference": "1783927027820dc1c624fd04abf5012a57f96feb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mailchimp/mailchimp-transactional-php/zipball/1783927027820dc1c624fd04abf5012a57f96feb",
+                "reference": "1783927027820dc1c624fd04abf5012a57f96feb",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "guzzlehttp/guzzle": "^6.4 || ^7.2",
+                "php": ">=7.2"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "~2.12",
+                "phpunit/phpunit": "^7",
+                "squizlabs/php_codesniffer": "~2.6"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "MailchimpTransactional\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "proprietary"
+            ],
+            "authors": [
+                {
+                    "name": "Mailchimp",
+                    "homepage": "https://github.com/mailchimp/mailchimp-transactional-php"
+                }
+            ],
+            "homepage": "http://swagger.io",
+            "keywords": [
+                "api",
+                "php",
+                "sdk",
+                "swagger"
+            ],
+            "support": {
+                "source": "https://github.com/mailchimp/mailchimp-transactional-php/tree/v1.0.59"
+            },
+            "time": "2024-02-10T01:12:26+00:00"
+        },
         {
             "name": "mailgun/mailgun-php",
             "version": "v4.1.0",
@@ -4247,16 +4301,16 @@
         },
         {
             "name": "smarty/smarty",
-            "version": "v4.3.5",
+            "version": "v4.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/smarty-php/smarty.git",
-                "reference": "e0cbbdf6ea21768d0194e59d2f8c2e20d5f0868c"
+                "reference": "f4152e9b814ae2369b6e4935c05e1e0c3654318d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/smarty-php/smarty/zipball/e0cbbdf6ea21768d0194e59d2f8c2e20d5f0868c",
-                "reference": "e0cbbdf6ea21768d0194e59d2f8c2e20d5f0868c",
+                "url": "https://api.github.com/repos/smarty-php/smarty/zipball/f4152e9b814ae2369b6e4935c05e1e0c3654318d",
+                "reference": "f4152e9b814ae2369b6e4935c05e1e0c3654318d",
                 "shasum": ""
             },
             "require": {
@@ -4307,35 +4361,35 @@
             "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.5"
+                "source": "https://github.com/smarty-php/smarty/tree/v4.4.1"
             },
-            "time": "2024-01-23T10:47:54+00:00"
+            "time": "2024-02-26T13:58:37+00:00"
         },
         {
             "name": "srmklive/paypal",
-            "version": "3.0.28",
+            "version": "3.0.30",
             "source": {
                 "type": "git",
                 "url": "https://github.com/srmklive/laravel-paypal.git",
-                "reference": "6bde2b232a38c413f730115743cc9dcc29d4228e"
+                "reference": "d511038ff01f466e7bfca475c7417b5a6c4d38a5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/srmklive/laravel-paypal/zipball/6bde2b232a38c413f730115743cc9dcc29d4228e",
-                "reference": "6bde2b232a38c413f730115743cc9dcc29d4228e",
+                "url": "https://api.github.com/repos/srmklive/laravel-paypal/zipball/d511038ff01f466e7bfca475c7417b5a6c4d38a5",
+                "reference": "d511038ff01f466e7bfca475c7417b5a6c4d38a5",
                 "shasum": ""
             },
             "require": {
                 "ext-curl": "*",
                 "guzzlehttp/guzzle": "~7.0",
-                "illuminate/support": "~6.0|~7.0|~8.0|~9.0|^10.0",
-                "nesbot/carbon": "~2.0",
+                "illuminate/support": "~6.0|~7.0|~8.0|~9.0|^10.0|^11.0",
+                "nesbot/carbon": "~2.0|^3.0",
                 "php": ">=7.2|^8.0"
             },
             "require-dev": {
                 "phpstan/phpstan": "^1.10",
                 "phpunit/phpunit": "^8.0|^9.0|^10.0",
-                "symfony/var-dumper": "~5.0"
+                "symfony/var-dumper": "~5.0|^7.0"
             },
             "type": "library",
             "extra": {
@@ -4373,9 +4427,9 @@
             ],
             "support": {
                 "issues": "https://github.com/srmklive/laravel-paypal/issues",
-                "source": "https://github.com/srmklive/laravel-paypal/tree/3.0.28"
+                "source": "https://github.com/srmklive/laravel-paypal/tree/3.0.30"
             },
-            "time": "2023-12-20T18:57:16+00:00"
+            "time": "2024-03-03T15:33:56+00:00"
         },
         {
             "name": "starkbank/ecdsa",
@@ -4422,16 +4476,16 @@
         },
         {
             "name": "stripe/stripe-php",
-            "version": "v13.12.0",
+            "version": "v13.13.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/stripe/stripe-php.git",
-                "reference": "8052da9058caae10c7297f85821f652b38e31d85"
+                "reference": "8dc58bab25f222a74d1157d4ca9e9e48451dd0c1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/stripe/stripe-php/zipball/8052da9058caae10c7297f85821f652b38e31d85",
-                "reference": "8052da9058caae10c7297f85821f652b38e31d85",
+                "url": "https://api.github.com/repos/stripe/stripe-php/zipball/8dc58bab25f222a74d1157d4ca9e9e48451dd0c1",
+                "reference": "8dc58bab25f222a74d1157d4ca9e9e48451dd0c1",
                 "shasum": ""
             },
             "require": {
@@ -4475,9 +4529,9 @@
             ],
             "support": {
                 "issues": "https://github.com/stripe/stripe-php/issues",
-                "source": "https://github.com/stripe/stripe-php/tree/v13.12.0"
+                "source": "https://github.com/stripe/stripe-php/tree/v13.13.0"
             },
-            "time": "2024-02-22T22:19:58+00:00"
+            "time": "2024-02-29T20:22:15+00:00"
         },
         {
             "name": "symfony/deprecation-contracts",
@@ -4548,16 +4602,16 @@
         },
         {
             "name": "symfony/http-client",
-            "version": "v7.0.3",
+            "version": "v7.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-client.git",
-                "reference": "3d2605c07cd14aec294f72f5bf8147702f7a5ada"
+                "reference": "8384876f49a2316a63f88a9cd12436de6936bee6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-client/zipball/3d2605c07cd14aec294f72f5bf8147702f7a5ada",
-                "reference": "3d2605c07cd14aec294f72f5bf8147702f7a5ada",
+                "url": "https://api.github.com/repos/symfony/http-client/zipball/8384876f49a2316a63f88a9cd12436de6936bee6",
+                "reference": "8384876f49a2316a63f88a9cd12436de6936bee6",
                 "shasum": ""
             },
             "require": {
@@ -4620,7 +4674,7 @@
                 "http"
             ],
             "support": {
-                "source": "https://github.com/symfony/http-client/tree/v7.0.3"
+                "source": "https://github.com/symfony/http-client/tree/v7.0.4"
             },
             "funding": [
                 {
@@ -4636,7 +4690,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-29T15:41:16+00:00"
+            "time": "2024-02-15T11:33:06+00:00"
         },
         {
             "name": "symfony/http-client-contracts",
@@ -5418,16 +5472,16 @@
         },
         {
             "name": "symfony/translation",
-            "version": "v6.4.3",
+            "version": "v6.4.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "637c51191b6b184184bbf98937702bcf554f7d04"
+                "reference": "bce6a5a78e94566641b2594d17e48b0da3184a8e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/637c51191b6b184184bbf98937702bcf554f7d04",
-                "reference": "637c51191b6b184184bbf98937702bcf554f7d04",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/bce6a5a78e94566641b2594d17e48b0da3184a8e",
+                "reference": "bce6a5a78e94566641b2594d17e48b0da3184a8e",
                 "shasum": ""
             },
             "require": {
@@ -5493,7 +5547,7 @@
             "description": "Provides tools to internationalize your application",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/translation/tree/v6.4.3"
+                "source": "https://github.com/symfony/translation/tree/v6.4.4"
             },
             "funding": [
                 {
@@ -5509,7 +5563,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-29T13:11:52+00:00"
+            "time": "2024-02-20T13:16:58+00:00"
         },
         {
             "name": "symfony/translation-contracts",
@@ -6507,16 +6561,16 @@
         },
         {
             "name": "friendsofphp/php-cs-fixer",
-            "version": "v3.50.0",
+            "version": "v3.51.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
-                "reference": "dbea11dcb6d9a1f6c8d51c0e580ab4a8876f524c"
+                "reference": "127fa74f010da99053e3f5b62672615b72dd6efd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/dbea11dcb6d9a1f6c8d51c0e580ab4a8876f524c",
-                "reference": "dbea11dcb6d9a1f6c8d51c0e580ab4a8876f524c",
+                "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/127fa74f010da99053e3f5b62672615b72dd6efd",
+                "reference": "127fa74f010da99053e3f5b62672615b72dd6efd",
                 "shasum": ""
             },
             "require": {
@@ -6587,7 +6641,7 @@
             ],
             "support": {
                 "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
-                "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.50.0"
+                "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.51.0"
             },
             "funding": [
                 {
@@ -6595,7 +6649,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2024-02-23T23:17:45+00:00"
+            "time": "2024-02-28T19:50:06+00:00"
         },
         {
             "name": "justinrainbow/json-schema",
@@ -6974,20 +7028,21 @@
         },
         {
             "name": "phar-io/manifest",
-            "version": "2.0.3",
+            "version": "2.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phar-io/manifest.git",
-                "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
+                "reference": "54750ef60c58e43759730615a392c31c80e23176"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
-                "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
+                "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+                "reference": "54750ef60c58e43759730615a392c31c80e23176",
                 "shasum": ""
             },
             "require": {
                 "ext-dom": "*",
+                "ext-libxml": "*",
                 "ext-phar": "*",
                 "ext-xmlwriter": "*",
                 "phar-io/version": "^3.0.1",
@@ -7028,9 +7083,15 @@
             "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
             "support": {
                 "issues": "https://github.com/phar-io/manifest/issues",
-                "source": "https://github.com/phar-io/manifest/tree/2.0.3"
+                "source": "https://github.com/phar-io/manifest/tree/2.0.4"
             },
-            "time": "2021-07-20T11:28:43+00:00"
+            "funding": [
+                {
+                    "url": "https://github.com/theseer",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-03T12:33:53+00:00"
         },
         {
             "name": "phar-io/version",
@@ -7189,16 +7250,16 @@
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "10.1.11",
+            "version": "10.1.12",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "78c3b7625965c2513ee96569a4dbb62601784145"
+                "reference": "842f72662d6b9edda84c4b6f13885fd9cd53dc63"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/78c3b7625965c2513ee96569a4dbb62601784145",
-                "reference": "78c3b7625965c2513ee96569a4dbb62601784145",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/842f72662d6b9edda84c4b6f13885fd9cd53dc63",
+                "reference": "842f72662d6b9edda84c4b6f13885fd9cd53dc63",
                 "shasum": ""
             },
             "require": {
@@ -7255,7 +7316,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
                 "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
-                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.11"
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.12"
             },
             "funding": [
                 {
@@ -7263,7 +7324,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-12-21T15:38:30+00:00"
+            "time": "2024-03-02T07:22:05+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
@@ -7510,16 +7571,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "10.5.10",
+            "version": "10.5.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c"
+                "reference": "0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/50b8e314b6d0dd06521dc31d1abffa73f25f850c",
-                "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4",
+                "reference": "0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4",
                 "shasum": ""
             },
             "require": {
@@ -7591,7 +7652,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.5.10"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.11"
             },
             "funding": [
                 {
@@ -7607,7 +7668,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-02-04T09:07:51+00:00"
+            "time": "2024-02-25T14:05:00+00:00"
         },
         {
             "name": "psr/cache",
@@ -7660,16 +7721,16 @@
         },
         {
             "name": "sebastian/cli-parser",
-            "version": "2.0.0",
+            "version": "2.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/cli-parser.git",
-                "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae"
+                "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae",
-                "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae",
+                "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084",
+                "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084",
                 "shasum": ""
             },
             "require": {
@@ -7704,7 +7765,8 @@
             "homepage": "https://github.com/sebastianbergmann/cli-parser",
             "support": {
                 "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
-                "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0"
+                "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+                "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1"
             },
             "funding": [
                 {
@@ -7712,7 +7774,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-02-03T06:58:15+00:00"
+            "time": "2024-03-02T07:12:49+00:00"
         },
         {
             "name": "sebastian/code-unit",
@@ -7962,16 +8024,16 @@
         },
         {
             "name": "sebastian/diff",
-            "version": "5.1.0",
+            "version": "5.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/diff.git",
-                "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f"
+                "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
-                "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
+                "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
                 "shasum": ""
             },
             "require": {
@@ -7979,7 +8041,7 @@
             },
             "require-dev": {
                 "phpunit/phpunit": "^10.0",
-                "symfony/process": "^4.2 || ^5"
+                "symfony/process": "^6.4"
             },
             "type": "library",
             "extra": {
@@ -8017,7 +8079,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/diff/issues",
                 "security": "https://github.com/sebastianbergmann/diff/security/policy",
-                "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0"
+                "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1"
             },
             "funding": [
                 {
@@ -8025,7 +8087,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-12-22T10:55:06+00:00"
+            "time": "2024-03-02T07:15:17+00:00"
         },
         {
             "name": "sebastian/environment",
@@ -8093,16 +8155,16 @@
         },
         {
             "name": "sebastian/exporter",
-            "version": "5.1.1",
+            "version": "5.1.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc"
+                "reference": "955288482d97c19a372d3f31006ab3f37da47adf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/64f51654862e0f5e318db7e9dcc2292c63cdbddc",
-                "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf",
+                "reference": "955288482d97c19a372d3f31006ab3f37da47adf",
                 "shasum": ""
             },
             "require": {
@@ -8159,7 +8221,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/exporter/issues",
                 "security": "https://github.com/sebastianbergmann/exporter/security/policy",
-                "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.1"
+                "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2"
             },
             "funding": [
                 {
@@ -8167,20 +8229,20 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-09-24T13:22:09+00:00"
+            "time": "2024-03-02T07:17:12+00:00"
         },
         {
             "name": "sebastian/global-state",
-            "version": "6.0.1",
+            "version": "6.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/global-state.git",
-                "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4"
+                "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/7ea9ead78f6d380d2a667864c132c2f7b83055e4",
-                "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
+                "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
                 "shasum": ""
             },
             "require": {
@@ -8214,14 +8276,14 @@
                 }
             ],
             "description": "Snapshotting of global state",
-            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "homepage": "https://www.github.com/sebastianbergmann/global-state",
             "keywords": [
                 "global state"
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/global-state/issues",
                 "security": "https://github.com/sebastianbergmann/global-state/security/policy",
-                "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.1"
+                "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2"
             },
             "funding": [
                 {
@@ -8229,7 +8291,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-07-19T07:19:23+00:00"
+            "time": "2024-03-02T07:19:19+00:00"
         },
         {
             "name": "sebastian/lines-of-code",
@@ -8720,16 +8782,16 @@
         },
         {
             "name": "symfony/cache",
-            "version": "v7.0.3",
+            "version": "v7.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/cache.git",
-                "reference": "2207eceb2433d74df81232d97439bf508cb9e050"
+                "reference": "fc822951dd360a593224bb2cef90a087d0dff60f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/cache/zipball/2207eceb2433d74df81232d97439bf508cb9e050",
-                "reference": "2207eceb2433d74df81232d97439bf508cb9e050",
+                "url": "https://api.github.com/repos/symfony/cache/zipball/fc822951dd360a593224bb2cef90a087d0dff60f",
+                "reference": "fc822951dd360a593224bb2cef90a087d0dff60f",
                 "shasum": ""
             },
             "require": {
@@ -8796,7 +8858,7 @@
                 "psr6"
             ],
             "support": {
-                "source": "https://github.com/symfony/cache/tree/v7.0.3"
+                "source": "https://github.com/symfony/cache/tree/v7.0.4"
             },
             "funding": [
                 {
@@ -8812,7 +8874,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-23T15:02:46+00:00"
+            "time": "2024-02-22T20:27:20+00:00"
         },
         {
             "name": "symfony/cache-contracts",
@@ -8892,16 +8954,16 @@
         },
         {
             "name": "symfony/console",
-            "version": "v7.0.3",
+            "version": "v7.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456"
+                "reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/c5010d50f1ee4b25cfa0201d9915cf1b14071456",
-                "reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456",
+                "url": "https://api.github.com/repos/symfony/console/zipball/6b099f3306f7c9c2d2786ed736d0026b2903205f",
+                "reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f",
                 "shasum": ""
             },
             "require": {
@@ -8965,7 +9027,7 @@
                 "terminal"
             ],
             "support": {
-                "source": "https://github.com/symfony/console/tree/v7.0.3"
+                "source": "https://github.com/symfony/console/tree/v7.0.4"
             },
             "funding": [
                 {
@@ -8981,7 +9043,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-23T15:02:46+00:00"
+            "time": "2024-02-22T20:27:20+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
@@ -9344,16 +9406,16 @@
         },
         {
             "name": "symfony/process",
-            "version": "v7.0.3",
+            "version": "v7.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "937a195147e0c27b2759ade834169ed006d0bc74"
+                "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/937a195147e0c27b2759ade834169ed006d0bc74",
-                "reference": "937a195147e0c27b2759ade834169ed006d0bc74",
+                "url": "https://api.github.com/repos/symfony/process/zipball/0e7727191c3b71ebec6d529fa0e50a01ca5679e9",
+                "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9",
                 "shasum": ""
             },
             "require": {
@@ -9385,7 +9447,7 @@
             "description": "Executes commands in sub-processes",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/process/tree/v7.0.3"
+                "source": "https://github.com/symfony/process/tree/v7.0.4"
             },
             "funding": [
                 {
@@ -9401,7 +9463,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-23T15:02:46+00:00"
+            "time": "2024-02-22T20:27:20+00:00"
         },
         {
             "name": "symfony/stopwatch",
@@ -9467,16 +9529,16 @@
         },
         {
             "name": "symfony/string",
-            "version": "v7.0.3",
+            "version": "v7.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/string.git",
-                "reference": "524aac4a280b90a4420d8d6a040718d0586505ac"
+                "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/string/zipball/524aac4a280b90a4420d8d6a040718d0586505ac",
-                "reference": "524aac4a280b90a4420d8d6a040718d0586505ac",
+                "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b",
+                "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b",
                 "shasum": ""
             },
             "require": {
@@ -9533,7 +9595,7 @@
                 "utf8"
             ],
             "support": {
-                "source": "https://github.com/symfony/string/tree/v7.0.3"
+                "source": "https://github.com/symfony/string/tree/v7.0.4"
             },
             "funding": [
                 {
@@ -9549,20 +9611,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-29T15:41:16+00:00"
+            "time": "2024-02-01T13:17:36+00:00"
         },
         {
             "name": "symfony/var-exporter",
-            "version": "v7.0.3",
+            "version": "v7.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-exporter.git",
-                "reference": "1fb79308cb5fc2b44bff6e8af10a5af6812e05b8"
+                "reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1fb79308cb5fc2b44bff6e8af10a5af6812e05b8",
-                "reference": "1fb79308cb5fc2b44bff6e8af10a5af6812e05b8",
+                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41",
+                "reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41",
                 "shasum": ""
             },
             "require": {
@@ -9607,7 +9669,7 @@
                 "serialize"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-exporter/tree/v7.0.3"
+                "source": "https://github.com/symfony/var-exporter/tree/v7.0.4"
             },
             "funding": [
                 {
@@ -9623,20 +9685,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-23T15:02:46+00:00"
+            "time": "2024-02-26T10:35:24+00:00"
         },
         {
             "name": "theseer/tokenizer",
-            "version": "1.2.2",
+            "version": "1.2.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/theseer/tokenizer.git",
-                "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
+                "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
-                "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
+                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+                "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
                 "shasum": ""
             },
             "require": {
@@ -9665,7 +9727,7 @@
             "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
             "support": {
                 "issues": "https://github.com/theseer/tokenizer/issues",
-                "source": "https://github.com/theseer/tokenizer/tree/1.2.2"
+                "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
             },
             "funding": [
                 {
@@ -9673,7 +9735,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-11-20T00:12:19+00:00"
+            "time": "2024-03-03T12:36:25+00:00"
         }
     ],
     "aliases": [],

+ 0 - 1
config/.config.example.php

@@ -19,7 +19,6 @@ $_ENV['checkNodeIp'] = true;               // 是否webapi验证节点ip
 // db_host|db_socket 二选一,若设置 db_socket 则 db_host 会被忽略,不用请留空
 // db_host 例: localhost(可解析的主机名), 127.0.0.1(IP 地址)
 // db_socket 例:/var/run/mysqld/mysqld.sock(需使用绝对地址)
-$_ENV['db_driver']    = 'mysql';
 $_ENV['db_host']      = '';
 $_ENV['db_socket']    = '';
 $_ENV['db_database']  = 'sspanel';           // 数据库名

+ 30 - 0
config/settings.json

@@ -519,6 +519,36 @@
         "default": "",
         "mark": "aws ses发送者"
     },
+    {
+        "id": null,
+        "item": "mailchimp_key",
+        "value": "",
+        "class": "email",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Mailchimp API密钥"
+    },
+    {
+        "id": null,
+        "item": "mailchimp_from_email",
+        "value": "",
+        "class": "email",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Mailchimp Email"
+    },
+    {
+        "id": null,
+        "item": "mailchimp_from_name",
+        "value": "",
+        "class": "email",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Mailchimp Sender"
+    },
     {
         "id": null,
         "item": "email_verify_code_ttl",

+ 2 - 1
db/migrations/2023020100-init.php

@@ -161,7 +161,8 @@ return new class() implements MigrationInterface {
                 `bandwidthlimit_resetday` tinyint(2) unsigned NOT NULL DEFAULT 0 COMMENT '流量重置日',
                 `node_heartbeat` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '节点心跳',
                 `online_user` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '节点在线用户',
-                `node_ip` varchar(255) NOT NULL DEFAULT '' COMMENT '节点IP',
+                `ipv4` INET4 NOT NULL DEFAULT '127.0.0.1' COMMENT 'IPv4地址',
+                `ipv6` INET6 NOT NULL DEFAULT '::1' COMMENT 'IPv6地址',
                 `node_group` smallint(5) unsigned NOT NULL DEFAULT 0 COMMENT '节点群组',
                 `online` tinyint(1) NOT NULL DEFAULT 1 COMMENT '在线状态',
                 `gfw_block` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '是否被GFW封锁',

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

@@ -80,7 +80,6 @@ return new class() implements MigrationInterface {
         ALTER TABLE node MODIFY COLUMN `bandwidthlimit_resetday` tinyint(2) unsigned NOT NULL DEFAULT 0 COMMENT '流量重置日';
         ALTER TABLE node MODIFY COLUMN `node_heartbeat` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '节点心跳';
         ALTER TABLE node MODIFY COLUMN `online_user` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '节点在线用户';
-        ALTER TABLE node MODIFY COLUMN `node_ip` varchar(255) NOT NULL DEFAULT '' COMMENT '节点IP';
         ALTER TABLE node MODIFY COLUMN `node_group` smallint(5) unsigned NOT NULL DEFAULT 0 COMMENT '节点群组';
         ALTER TABLE node MODIFY COLUMN `online` tinyint(1) NOT NULL DEFAULT 1 COMMENT '在线状态';
         ALTER TABLE node MODIFY COLUMN `gfw_block` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '是否被GFW封锁';

+ 30 - 0
db/migrations/2024030300-update_node_ip.php

@@ -0,0 +1,30 @@
+<?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("
+            ALTER TABLE node ADD COLUMN IF NOT EXISTS `ipv4` INET4 NOT NULL DEFAULT '127.0.0.1' COMMENT 'IPv4地址';
+            ALTER TABLE node ADD COLUMN IF NOT EXISTS `ipv6` INET6 NOT NULL DEFAULT '::1' COMMENT 'IPv6地址';
+            ALTER TABLE node DROP COLUMN IF EXISTS `node_ip`;
+        ");
+
+        return 2024030300;
+    }
+
+    public function down(): int
+    {
+        DB::getPdo()->exec("
+            ALTER TABLE node ADD COLUMN IF NOT EXISTS `node_ip` varchar(255) NOT NULL DEFAULT '' COMMENT '节点IP';
+            ALTER TABLE node DROP COLUMN IF EXISTS `ipv4`;
+            ALTER TABLE node DROP COLUMN IF EXISTS `ipv6`;
+        ");
+
+        return 2024021900;
+    }
+};

+ 8 - 2
resources/views/tabler/admin/node/edit.tpl

@@ -48,9 +48,15 @@
                                 </div>
                             </div>
                             <div class="form-group mb-3 row">
-                                <label class="form-label col-3 col-form-label">服务器IP</label>
+                                <label class="form-label col-3 col-form-label">IPv4地址</label>
                                 <div class="col">
-                                    <input id="node_ip" type="text" class="form-control" value="{$node->node_ip}" disabled>
+                                    <input type="text" class="form-control" value="{$node->ipv4}" disabled>
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-3 col-form-label">IPv6地址</label>
+                                <div class="col">
+                                    <input type="text" class="form-control" value="{$node->ipv6}" disabled>
                                 </div>
                             </div>
                             <div class="form-group mb-3 row">

+ 61 - 31
resources/views/tabler/admin/setting/email.tpl

@@ -40,10 +40,10 @@
                                     <a href="#smtp" class="nav-link" data-bs-toggle="tab">SMTP</a>
                                 </li>
                                 <li class="nav-item">
-                                    <a href="#sendgrid" class="nav-link" data-bs-toggle="tab">Sendgrid</a>
+                                    <a href="#mailgun" class="nav-link" data-bs-toggle="tab">Mailgun</a>
                                 </li>
                                 <li class="nav-item">
-                                    <a href="#mailgun" class="nav-link" data-bs-toggle="tab">Mailgun</a>
+                                    <a href="#sendgrid" class="nav-link" data-bs-toggle="tab">Sendgrid</a>
                                 </li>
                                 <li class="nav-item">
                                     <a href="#postal" class="nav-link" data-bs-toggle="tab">Postal</a>
@@ -51,6 +51,8 @@
                                 <li class="nav-item">
                                     <a href="#ses" class="nav-link" data-bs-toggle="tab">AWS SES</a>
                                 </li>
+                                <li class="nav-item">
+                                    <a href="#mailchimp" class="nav-link" data-bs-toggle="tab">Mailchimp</a>
                             </ul>
                         </div>
                         <div class="card-body">
@@ -68,14 +70,14 @@
                                                     <option value="smtp"
                                                             {if $settings['email_driver'] === "smtp"}selected{/if}>smtp
                                                     </option>
-                                                    <option value="sendgrid"
-                                                            {if $settings['email_driver'] === "sendgrid"}selected{/if}>
-                                                        sendgrid
-                                                    </option>
                                                     <option value="mailgun"
                                                             {if $settings['email_driver'] === "mailgun"}selected{/if}>
                                                         mailgun
                                                     </option>
+                                                    <option value="sendgrid"
+                                                            {if $settings['email_driver'] === "sendgrid"}selected{/if}>
+                                                        sendgrid
+                                                    </option>
                                                     <option value="postal"
                                                             {if $settings['email_driver'] === "postal"}selected{/if}>
                                                         postal
@@ -83,6 +85,9 @@
                                                     <option value="ses"
                                                             {if $settings['email_driver'] === "ses"}selected{/if}>ses
                                                     </option>
+                                                    <option value="mailchimp"
+                                                            {if $settings['email_driver'] === "mailchimp"}selected{/if}>ses
+                                                    </option>
                                                 </select>
                                             </div>
                                         </div>
@@ -214,31 +219,6 @@
                                         </div>
                                     </div>
                                 </div>
-                                <div class="tab-pane" id="sendgrid">
-                                    <div class="card-body">
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Sendgrid 密钥</label>
-                                            <div class="col">
-                                                <input id="sendgrid_key" type="text" class="form-control"
-                                                       value="{$settings['sendgrid_key']}">
-                                            </div>
-                                        </div>
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Sendgrid 发信地址</label>
-                                            <div class="col">
-                                                <input id="sendgrid_sender" type="text" class="form-control"
-                                                       value="{$settings['sendgrid_sender']}">
-                                            </div>
-                                        </div>
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Sendgrid 发件人名称</label>
-                                            <div class="col">
-                                                <input id="sendgrid_name" type="text" class="form-control"
-                                                       value="{$settings['sendgrid_name']}">
-                                            </div>
-                                        </div>
-                                    </div>
-                                </div>
                                 <div class="tab-pane" id="mailgun">
                                     <div class="card-body">
                                         <div class="form-group mb-3 row">
@@ -271,6 +251,31 @@
                                         </div>
                                     </div>
                                 </div>
+                                <div class="tab-pane" id="sendgrid">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Sendgrid 密钥</label>
+                                            <div class="col">
+                                                <input id="sendgrid_key" type="text" class="form-control"
+                                                       value="{$settings['sendgrid_key']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Sendgrid 发信地址</label>
+                                            <div class="col">
+                                                <input id="sendgrid_sender" type="text" class="form-control"
+                                                       value="{$settings['sendgrid_sender']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Sendgrid 发件人名称</label>
+                                            <div class="col">
+                                                <input id="sendgrid_name" type="text" class="form-control"
+                                                       value="{$settings['sendgrid_name']}">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
                                 <div class="tab-pane" id="postal">
                                     <div class="card-body">
                                         <div class="form-group mb-3 row">
@@ -335,6 +340,31 @@
                                         </div>
                                     </div>
                                 </div>
+                                <div class="tab-pane" id="mailchimp">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Mailchimp API密钥</label>
+                                            <div class="col">
+                                                <input id="mailchimp_key" type="text" class="form-control"
+                                                       value="{$settings['mailchimp_key']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Mailchimp发件邮箱</label>
+                                            <div class="col">
+                                                <input id="mailchimp_from_email" type="text" class="form-control"
+                                                       value="{$settings['mailchimp_from_email']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Mailchimp发件人名称</label>
+                                            <div class="col">
+                                                <input id="mailchimp_from_name" type="text" class="form-control"
+                                                       value="{$settings['mailchimp_from_name']}">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
                             </div>
                         </div>
                     </div>

+ 7 - 5
resources/views/tabler/user/index.tpl

@@ -567,11 +567,13 @@
                                         {/if}
                                         <button id="check-in" class="btn btn-primary ms-auto"
                                                 hx-post="/user/checkin" hx-swap="none"
-                                                {if $public_setting['captcha_provider'] === 'turnstile'}
-                                                    hx-vals='js:{ turnstile: document.querySelector("[name=cf-turnstile-response]").value }'
-                                                {/if}
-                                                {if $public_setting['captcha_provider'] === 'geetest'}
-                                                    hx-vals='js:{ geetest: geetest_result }'
+                                                {if $public_setting['enable_checkin_captcha']}
+                                                    {if $public_setting['captcha_provider'] === 'turnstile'}
+                                                        hx-vals='js:{ turnstile: document.querySelector("[name=cf-turnstile-response]").value }'
+                                                    {/if}
+                                                    {if $public_setting['captcha_provider'] === 'geetest'}
+                                                        hx-vals='js:{ geetest: geetest_result }'
+                                                    {/if}
                                                 {/if}>
                                             签到
                                         </button>

+ 2 - 4
resources/views/tabler/user/ticket/index.tpl

@@ -54,9 +54,7 @@
                                         </div>
                                         <div class="card-footer">
                                             <div class="d-flex">
-                                                <!-- 工单状态标签 -->
                                                 <span class="status status-grey">{$ticket->status}</span>
-                                                <!-- 工单类型标签 -->
                                                 <span class="status status-grey">{$ticket->type}</span>
                                                 <a href="/user/ticket/{$ticket->id}/view"
                                                    class="btn btn-primary ms-auto">查看</a>
@@ -109,8 +107,8 @@
                     <button id="create-ticket-button" class="btn btn-primary" data-bs-dismiss="modal"
                             hx-post="/user/ticket" hx-swap="none"
                             hx-vals='js:{
-                            title: document.getElementById("ticket-title").value
-                            comment: document.getElementById("ticket-comment").value
+                            title: document.getElementById("ticket-title").value,
+                            comment: document.getElementById("ticket-comment").value,
                             type: document.getElementById("ticket-type").value }'>
                         创建
                     </button>

+ 0 - 13
src/Command/ClientDownload.php

@@ -62,10 +62,6 @@ final class ClientDownload extends Command
 
     /**
      * 下载远程文件
-     *
-     * @param string $fileName
-     * @param string $savePath
-     * @param string $url
      */
     private function getSourceFile(string $fileName, string $savePath, string $url): bool
     {
@@ -98,8 +94,6 @@ final class ClientDownload extends Command
     /**
      * 获取 GitHub 常规 Release
      *
-     * @param string $repo
-     *
      * @throws GuzzleException
      */
     private function getLatestReleaseTagName(string $repo): string
@@ -117,8 +111,6 @@ final class ClientDownload extends Command
     /**
      * 获取 GitHub Pre-Release
      *
-     * @param string $repo
-     *
      * @throws GuzzleException
      */
     private function getLatestPreReleaseTagName(string $repo): string
@@ -190,11 +182,6 @@ final class ClientDownload extends Command
         );
     }
 
-    /**
-     * @param $name
-     * @param $taskName
-     * @param $tagName
-     */
     private static function getNames($name, $taskName, $tagName): array|string
     {
         return str_replace(

+ 2 - 2
src/Command/Migration.php

@@ -112,7 +112,7 @@ END;
             krsort($queue);
 
             foreach ($queue as $version => $object) {
-                echo 'Reverse on ' . $version . PHP_EOL;
+                echo 'Rollback to ' . $version . PHP_EOL;
                 $current = $object->down();
             }
         } else {
@@ -141,6 +141,6 @@ END;
         $count = count($queue);
 
         echo 'Migration completed. ' . $count . ' file(s) processed.' . PHP_EOL
-            . 'Current version: ' . $current . PHP_EOL;
+            . 'Current database version: ' . $current . PHP_EOL;
     }
 }

+ 0 - 4
src/Controllers/Admin/AnnController.php

@@ -67,10 +67,6 @@ final class AnnController extends BaseController
 
     /**
      * 后台添加公告
-     *
-     * @param ServerRequest $request
-     * @param Response $response
-     * @param array $args
      */
     public function add(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {

+ 0 - 4
src/Controllers/Admin/DocsController.php

@@ -121,10 +121,6 @@ final class DocsController extends BaseController
 
     /**
      * 后台编辑文档提交
-     *
-     * @param ServerRequest $request
-     * @param Response $response
-     * @param array $args
      */
     public function update(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {

+ 4 - 0
src/Controllers/Admin/Setting/EmailController.php

@@ -46,6 +46,10 @@ final class EmailController extends BaseController
         'postal_key',
         'postal_sender',
         'postal_name',
+        // Mailchimp
+        'mailchimp_key',
+        'mailchimp_from_email',
+        'mailchimp_from_name',
     ];
 
     /**

+ 0 - 4
src/Controllers/Admin/UserController.php

@@ -104,10 +104,6 @@ final class UserController extends BaseController
     }
 
     /**
-     * @param ServerRequest $request
-     * @param Response $response
-     * @param array $args
-     *
      * @throws Exception
      */
     public function create(ServerRequest $request, Response $response, array $args): Response|ResponseInterface

+ 0 - 19
src/Controllers/AuthController.php

@@ -53,11 +53,6 @@ final class AuthController extends BaseController
             ->fetch('auth/login.tpl'));
     }
 
-    /**
-     * @param ServerRequest $request
-     * @param Response $response
-     * @param array $args
-     */
     public function loginHandle(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         if (Config::obtain('enable_login_captcha') && ! Captcha::verify($request->getParams())) {
@@ -197,16 +192,6 @@ final class AuthController extends BaseController
     }
 
     /**
-     * @param Response $response
-     * @param $name
-     * @param $email
-     * @param $passwd
-     * @param $invite_code
-     * @param $imtype
-     * @param $imvalue
-     * @param $money
-     * @param $is_admin_reg
-     *
      * @throws Exception
      */
     public function registerHelper(
@@ -297,10 +282,6 @@ final class AuthController extends BaseController
     }
 
     /**
-     * @param ServerRequest $request
-     * @param Response $response
-     * @param array $args
-     *
      * @throws RedisException
      * @throws Exception
      */

+ 0 - 4
src/Controllers/SubController.php

@@ -21,10 +21,6 @@ use function strtotime;
 final class SubController extends BaseController
 {
     /**
-     * @param $request
-     * @param $response
-     * @param $args
-     *
      * @throws ClientExceptionInterface
      * @throws GuzzleException
      * @throws RedisException

+ 6 - 0
src/Controllers/WebAPI/FuncController.php

@@ -13,11 +13,17 @@ use Slim\Http\ServerRequest;
 
 final class FuncController extends BaseController
 {
+    /**
+     * GET /mod_mu/func/ping
+     */
     public function ping(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         return ResponseHelper::success($response, 'Pong? Pong!');
     }
 
+    /**
+     * GET /mod_mu/func/detect_rules
+     */
     public function getDetectRules(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         $rules = DetectRule::all()->toArray();

+ 3 - 0
src/Controllers/WebAPI/NodeController.php

@@ -16,6 +16,9 @@ use const VERSION;
 
 final class NodeController extends BaseController
 {
+    /**
+     * GET /mod_mu/nodes/{id}/info
+     */
     public function getInfo(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         $node_id = $args['id'];

+ 0 - 16
src/Controllers/WebAPI/UserController.php

@@ -27,10 +27,6 @@ final class UserController extends BaseController
 {
     /**
      * GET /mod_mu/users
-     *
-     * @param ServerRequest   $request
-     * @param Response  $response
-     * @param array     $args
      */
     public function index(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
@@ -136,10 +132,6 @@ final class UserController extends BaseController
 
     /**
      * POST /mod_mu/users/traffic
-     *
-     * @param ServerRequest   $request
-     * @param Response  $response
-     * @param array     $args
      */
     public function addTraffic(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
@@ -223,10 +215,6 @@ final class UserController extends BaseController
 
     /**
      * POST /mod_mu/users/aliveip
-     *
-     * @param ServerRequest   $request
-     * @param Response  $response
-     * @param array     $args
      */
     public function addAliveIp(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
@@ -278,10 +266,6 @@ final class UserController extends BaseController
 
     /**
      * POST /mod_mu/users/detectlog
-     *
-     * @param ServerRequest   $request
-     * @param Response  $response
-     * @param array     $args
      */
     public function addDetectLog(ServerRequest $request, Response $response, array $args): ResponseInterface
     {

+ 3 - 1
src/Middleware/NodeToken.php

@@ -55,7 +55,9 @@ final class NodeToken implements MiddlewareInterface
         if ($_ENV['checkNodeIp']) {
             $ip = $request->getServerParam('REMOTE_ADDR');
 
-            if ($ip !== '127.0.0.1' && ! (new Node())->where('node_ip', $ip)->exists()) {
+            if ($ip !== '127.0.0.1' && $ip !== '::1' && $ip !== '0:0:0:0:0:0:0:1' &&
+                ! (new Node())->where('ipv4', $ip)->orWhere('ipv6', $ip)->exists()
+            ) {
                 return AppFactory::determineResponseFactory()->createResponse(401)->withJson([
                     'ret' => 0,
                     'msg' => 'Invalid request IP.',

+ 13 - 6
src/Models/Node.php

@@ -30,7 +30,8 @@ use const DNS_AAAA;
  * @property int    $bandwidthlimit_resetday 流量重置日
  * @property int    $node_heartbeat          节点心跳
  * @property int    $online_user             节点在线用户
- * @property string $node_ip                 节点IP
+ * @property string $ipv4                    IPv4地址
+ * @property string $ipv6                    IPv6地址
  * @property int    $node_group              节点群组
  * @property int    $online                  在线状态
  * @property int    $gfw_block               是否被GFW封锁
@@ -100,7 +101,7 @@ final class Node extends Model
     /**
      * 获取节点在线状态
      *
-     * @return int 0 = new node OR -1 = offline OR 1 = online
+     * @return int 0 = new node, -1 = offline, 1 = online
      */
     public function getNodeOnlineStatus(): int
     {
@@ -112,14 +113,20 @@ final class Node extends Model
      */
     public function updateNodeIp(): void
     {
-        if (Tools::isIPv4($this->server) || Tools::isIPv6($this->server)) {
-            $this->node_ip = $this->server;
+        if (Tools::isIPv4($this->server)) {
+            $this->ipv4 = $this->server;
+            $this->ipv6 = '::1';
+        } elseif (Tools::isIPv6($this->server)) {
+            $this->ipv4 = '127.0.0.1';
+            $this->ipv6 = $this->server;
         } else {
             try {
                 $result = dns_get_record($this->server, DNS_A + DNS_AAAA);
-                $this->node_ip = $result[0]['ip'] ?? $result[0]['ipv6'] ?? $this->server;
+                $this->ipv4 = $result[0]['ip'] ?? '127.0.0.1';
+                $this->ipv6 = $result[1]['ipv6'] ?? '::1';
             } catch (Exception $e) {
-                $this->node_ip = $this->server;
+                $this->ipv4 = '127.0.0.1';
+                $this->ipv6 = '::1';
             }
         }
     }

+ 0 - 3
src/Models/User.php

@@ -94,9 +94,6 @@ final class User extends Model
         'ref_by' => 'int',
     ];
 
-    /**
-     * @param $len
-     */
     public function getSs2022Pk($len): string
     {
         return Tools::genSs2022UserPk($this->passwd, $len);

+ 0 - 2
src/Services/Analytics.php

@@ -22,8 +22,6 @@ final class Analytics
 {
     /**
      * 获取累计收入
-     *
-     * @param string $req
      */
     public static function getIncome(string $req): float
     {

+ 3 - 3
src/Services/Auth/Cookie.php

@@ -54,10 +54,10 @@ final class Cookie extends Base
         }
 
         if ($_ENV['enable_login_bind_ip']) {
-            $remote_ip = $_SERVER['REMOTE_ADDR'];
-            $node = (new Node())->where('node_ip', $remote_ip)->first();
+            $ip = $_SERVER['REMOTE_ADDR'];
+            $node = (new Node())->where('ipv4', $ip)->orWhere('ipv6', $ip)->first();
 
-            if ($node === null && $ipHash !== Hash::ipHash($remote_ip, $uid, $expire_in)) {
+            if ($node === null && $ipHash !== Hash::ipHash($ip, $uid, $expire_in)) {
                 return $user;
             }
         }

+ 8 - 3
src/Services/DB.php

@@ -6,6 +6,7 @@ namespace App\Services;
 
 use Exception;
 use Illuminate\Database\Capsule\Manager;
+use const PHP_EOL;
 
 final class DB extends Manager
 {
@@ -17,7 +18,11 @@ final class DB extends Manager
             $db->addConnection(self::getConfig());
             $db->getConnection()->getPdo();
         } catch (Exception $e) {
-            die('Could not connect to main database: ' . $e->getMessage());
+            if ($_ENV['debug']) {
+                die('Databse Error' . PHP_EOL . 'Reason: ' . $e->getMessage());
+            }
+
+            die('Databse Error');
         }
 
         $db->setAsGlobal();
@@ -31,7 +36,7 @@ final class DB extends Manager
     {
         if ($_ENV['enable_db_rw_split']) {
             return [
-                'driver' => $_ENV['db_driver'],
+                'driver' => 'mysql',
                 'read' => [
                     'host' => $_ENV['read_db_hosts'],
                 ],
@@ -50,7 +55,7 @@ final class DB extends Manager
         }
 
         return [
-            'driver' => $_ENV['db_driver'],
+            'driver' => 'mysql',
             'host' => $_ENV['db_host'],
             'unix_socket' => $_ENV['db_socket'],
             'database' => $_ENV['db_database'],

+ 3 - 2
src/Services/Detect.php

@@ -29,12 +29,13 @@ final class Detect
      */
     public static function gfw(): void
     {
-        $nodes = (new Node())->where('type', 1)->where('node_ip', '!=', '')->where('online', 1)->get();
+        $nodes = (new Node())->where('type', 1)
+            ->where('ipv4', '!=', '127.0.0.1')->where('online', 1)->get();
 
         foreach ($nodes as $node) {
             $api_url = str_replace(
                 ['{ip}', '{port}'],
-                [$node->node_ip, $_ENV['detect_gfw_port']],
+                [$node->ipv4, $_ENV['detect_gfw_port']],
                 $_ENV['detect_gfw_url']
             );
 

+ 8 - 7
src/Services/Mail.php

@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace App\Services;
 
 use App\Models\Config;
+use App\Services\Mail\Mailchimp;
 use App\Services\Mail\Mailgun;
 use App\Services\Mail\NullMail;
 use App\Services\Mail\Postal;
@@ -20,15 +21,16 @@ use Smarty;
  */
 final class Mail
 {
-    public static function getClient(): Mailgun|Smtp|SendGrid|NullMail|Ses|Postal
+    public static function getClient(): Mailchimp|Mailgun|NullMail|Postal|SendGrid|Ses|Smtp
     {
         $driver = Config::obtain('email_driver');
         return match ($driver) {
+            'mailchimp' => new Mailchimp(),
             'mailgun' => new Mailgun(),
+            'postal' => new Postal(),
+            'sendgrid' => new SendGrid(),
             'ses' => new Ses(),
             'smtp' => new Smtp(),
-            'sendgrid' => new SendGrid(),
-            'postal' => new Postal(),
             default => new NullMail(),
         };
     }
@@ -42,7 +44,6 @@ final class Mail
         $smarty->settemplatedir(BASE_PATH . '/resources/email/');
         $smarty->setcompiledir(BASE_PATH . '/storage/framework/smarty/compile/');
         $smarty->setcachedir(BASE_PATH . '/storage/framework/smarty/cache/');
-        // add config
         $smarty->assign('config', View::getConfig());
 
         foreach ($ary as $key => $value) {
@@ -56,10 +57,10 @@ final class Mail
      * @throws Exception
      * @throws ClientExceptionInterface
      */
-    public static function send($to, $subject, $template, $ary = [], $files = []): void
+    public static function send($to, $subject, $template, $array = []): void
     {
-        $text = self::genHtml($template, $ary);
+        $body = self::genHtml($template, $array);
 
-        self::getClient()->send($to, $subject, $text, $files);
+        self::getClient()->send($to, $subject, $body);
     }
 }

+ 1 - 1
src/Services/Mail/Base.php

@@ -6,5 +6,5 @@ namespace App\Services\Mail;
 
 abstract class Base
 {
-    abstract public function send($to, $subject, $text, $files): void;
+    abstract public function send($to, $subject, $body): void;
 }

+ 38 - 0
src/Services/Mail/Mailchimp.php

@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Services\Mail;
+
+use App\Models\Config;
+use MailchimpTransactional\ApiClient;
+
+final class Mailchimp extends Base
+{
+    private ApiClient $mc;
+    private string $from_email;
+    private string $from_name;
+
+    public function __construct()
+    {
+        $configs = Config::getClass('email');
+
+        $this->mc = new ApiClient();
+        $this->mc->setApiKey($configs['mailchimp_key']);
+        $this->from_email = $configs['mailchimp_from_email'];
+        $this->from_name = $configs['mailchimp_from_name'];
+    }
+
+    public function send($to, $subject, $body): void
+    {
+        $this->mc->messages->send([
+            'message' => [
+                'html' => $body,
+                'subject' => $subject,
+                'from_email' => $this->from_email,
+                'from_name' => $this->from_name,
+                'to' => [['email' => $to]],
+            ],
+        ]);
+    }
+}

+ 4 - 14
src/Services/Mail/Mailgun.php

@@ -8,13 +8,12 @@ use App\Models\Config;
 use Exception;
 use Mailgun\Mailgun as MG;
 use Psr\Http\Client\ClientExceptionInterface;
-use function basename;
 
 final class Mailgun extends Base
 {
     private MG $mg;
-    private mixed $domain;
-    private mixed $sender;
+    private string $domain;
+    private string $sender;
 
     public function __construct()
     {
@@ -29,22 +28,13 @@ final class Mailgun extends Base
      * @throws Exception
      * @throws ClientExceptionInterface
      */
-    public function send($to, $subject, $text, $files): void
+    public function send($to, $subject, $body): void
     {
-        $inline = [];
-
-        if ($files !== []) {
-            foreach ($files as $file_raw) {
-                $inline[] = ['filePath' => $file_raw, 'filename' => basename($file_raw)];
-            }
-        }
-
         $this->mg->messages()->send($this->domain, [
             'from' => $this->sender,
             'to' => $to,
             'subject' => $subject,
-            'html' => $text,
-            'inline' => $inline,
+            'html' => $body,
         ]);
     }
 }

+ 1 - 1
src/Services/Mail/NullMail.php

@@ -16,7 +16,7 @@ final class NullMail extends Base
         ];
     }
 
-    public function send($to, $subject, $text, $files): void
+    public function send($to, $subject, $body): void
     {
         echo '';
     }

+ 3 - 11
src/Services/Mail/Postal.php

@@ -7,8 +7,6 @@ namespace App\Services\Mail;
 use App\Models\Config;
 use Postal\Client;
 use Postal\Send\Message;
-use function basename;
-use function mime_content_type;
 
 final class Postal extends Base
 {
@@ -26,18 +24,12 @@ final class Postal extends Base
         $this->message->replyTo($configs['postal_sender']);
     }
 
-    public function send($to, $subject, $text, $files): void
+    public function send($to, $subject, $body): void
     {
         $this->message->subject($subject);
         $this->message->to($to);
-        $this->message->plainBody($text);
-        $this->message->htmlBody($text);
-
-        if ($files !== []) {
-            foreach ($files as $file_raw) {
-                $this->message->attach(basename($file_raw), mime_content_type($file_raw), $file_raw);
-            }
-        }
+        $this->message->plainBody($body);
+        $this->message->htmlBody($body);
 
         $this->client->send->message($this->message);
     }

+ 4 - 20
src/Services/Mail/SendGrid.php

@@ -8,9 +8,6 @@ use App\Models\Config;
 use SendGrid as SG;
 use SendGrid\Mail\Mail;
 use SendGrid\Mail\TypeException;
-use function base64_encode;
-use function basename;
-use function file_get_contents;
 
 final class SendGrid extends Base
 {
@@ -32,25 +29,12 @@ final class SendGrid extends Base
     /**
      * @throws TypeException
      */
-    public function send($to, $subject, $text, $files): void
+    public function send($to, $subject, $body): void
     {
         $this->email->setSubject($subject);
         $this->email->addTo($to);
-        $this->email->addContent('text/html', $text);
-
-        if ($files !== []) {
-            foreach ($files as $file_raw) {
-                $this->email->addAttachment(
-                    base64_encode(file_get_contents($file_raw)),
-                    'application/octet-stream',
-                    basename($file_raw),
-                    'attachment',
-                    'attachment'
-                );
-            }
-        }
-
-        $response = $this->sg->send($this->email);
-        echo $response->body();
+        $this->email->addContent('text/html', $body);
+
+        $this->sg->send($this->email);
     }
 }

+ 2 - 6
src/Services/Mail/Ses.php

@@ -27,7 +27,7 @@ final class Ses extends Base
         $this->ses = $ses;
     }
 
-    public function send($to, $subject, $text, $files): void
+    public function send($to, $subject, $body): void
     {
         $ses = $this->ses;
         $char_set = 'UTF-8';
@@ -41,11 +41,7 @@ final class Ses extends Base
                 'Body' => [
                     'Html' => [
                         'Charset' => $char_set,
-                        'Data' => $text,
-                    ],
-                    'Text' => [
-                        'Charset' => $char_set,
-                        'Data' => $text,
+                        'Data' => $body,
                     ],
                 ],
                 'Subject' => [

+ 10 - 20
src/Services/Mail/Smtp.php

@@ -20,18 +20,16 @@ final class Smtp extends Base
         $configs = Config::getClass('email');
 
         $mail = new PHPMailer();
-        //$mail->SMTPDebug = 3;                               // Enable verbose debug output
-        $mail->isSMTP();                                      // Set mailer to use SMTP
-        $mail->Host = $configs['smtp_host'];                  // Specify main and backup SMTP servers
-        $mail->Port = $configs['smtp_port'];                  // TCP port to connect to
-        $mail->SMTPAuth = true;                               // Enable SMTP authentication
-        $mail->CharSet = 'UTF-8';                             // Set utf-8 character set
-        $mail->Username = $configs['smtp_username'];          // SMTP username
-        $mail->Password = $configs['smtp_password'];          // SMTP password
+        $mail->isSMTP();
+        $mail->Host = $configs['smtp_host'];
+        $mail->Port = $configs['smtp_port'];
+        $mail->SMTPAuth = ! ($configs['smtp_username'] === '' && $configs['smtp_password'] === '');
+        $mail->CharSet = 'UTF-8';
+        $mail->Username = $configs['smtp_username'];
+        $mail->Password = $configs['smtp_password'];
         $mail->setFrom($configs['smtp_sender'], $configs['smtp_name']);
 
         if ($configs['smtp_ssl']) {
-            // Enable TLS encryption, `ssl` also accepted
             $mail->SMTPSecure = ($configs['smtp_port'] === '587' ? 'tls' : 'ssl');
         }
 
@@ -45,22 +43,14 @@ final class Smtp extends Base
     /**
      * @throws Exception
      */
-    public function send($to, $subject, $text, $files): void
+    public function send($to, $subject, $body): void
     {
         $mail = $this->mail;
         $mail->addAddress($to);     // Add a recipient
         $mail->isHTML();
         $mail->Subject = $subject;
-        $mail->Body = $text;
+        $mail->Body = $body;
 
-        if ($files !== []) {
-            foreach ($files as $file_raw) {
-                $mail->addAttachment($file_raw);
-            }
-        }
-
-        if (! $mail->send()) {
-            throw new Exception($mail->ErrorInfo);
-        }
+        $mail->send();
     }
 }

+ 0 - 4
src/Services/Subscribe.php

@@ -35,10 +35,6 @@ final class Subscribe
         return $_ENV['subUrl'] . '/sub/' . $token->token;
     }
 
-    /**
-     * @param $user
-     * @param bool $show_all_nodes
-     */
     public static function getUserNodes($user, bool $show_all_nodes = false): Collection
     {
         $query = Node::query();

+ 0 - 12
src/Utils/ResponseHelper.php

@@ -20,11 +20,6 @@ final class ResponseHelper
         ]);
     }
 
-    /**
-     * @param Response $response
-     * @param string $msg
-     * @param array $data
-     */
     public static function successWithData(Response $response, string $msg = '', array $data = []): ResponseInterface
     {
         return $response->withJson([
@@ -39,8 +34,6 @@ final class ResponseHelper
      *
      * **Note**: `RequestInterface` or `ResponseInterface` shouldn't be modified before/after calling this function.
      *
-     * @param RequestInterface $request
-     * @param ResponseInterface $response
      * @param mixed $data
      */
     public static function successWithDataEtag(
@@ -68,11 +61,6 @@ final class ResponseHelper
         ]);
     }
 
-    /**
-     * @param Response $response
-     * @param string $msg
-     * @param array $data
-     */
     public static function errorWithData(Response $response, string $msg = '', array $data = []): ResponseInterface
     {
         return $response->withJson([

+ 0 - 38
src/Utils/Tools.php

@@ -46,8 +46,6 @@ final class Tools
 {
     /**
      * 查询IP归属
-     *
-     * @param string $ip
      */
     public static function getIpLocation(string $ip): string
     {
@@ -88,9 +86,6 @@ final class Tools
 
     /**
      * 根据流量值自动转换单位输出
-     *
-     * @param $size
-     * @param int $precision
      */
     public static function autoBytes($size, int $precision = 2): string
     {
@@ -110,8 +105,6 @@ final class Tools
 
     /**
      * 根据含单位的流量值转换 B 输出
-     *
-     * @param $size
      */
     public static function autoBytesR($size): ?int
     {
@@ -132,9 +125,6 @@ final class Tools
 
     /**
      * 根据速率值自动转换单位输出
-     *
-     * @param $size
-     * @param int $precision
      */
     public static function autoMbps($size, int $precision = 2): string
     {
@@ -154,8 +144,6 @@ final class Tools
 
     /**
      * 虽然名字是toMB,但是实际上功能是from MB to B
-     *
-     * @param $traffic
      */
     public static function toMB($traffic): int
     {
@@ -164,25 +152,17 @@ final class Tools
 
     /**
      * 虽然名字是toGB,但是实际上功能是from GB to B
-     *
-     * @param $traffic
      */
     public static function toGB($traffic): int
     {
         return (int) $traffic * 1073741824;
     }
 
-    /**
-     * @param $traffic
-     */
     public static function flowToMB($traffic): float
     {
         return round($traffic / 1048576, 2);
     }
 
-    /**
-     * @param $traffic
-     */
     public static function flowToGB($traffic): float
     {
         return round($traffic / 1073741824, 2);
@@ -275,10 +255,6 @@ final class Tools
         return $dirArray;
     }
 
-    /**
-     * @param $type
-     * @param $str
-     */
     public static function isParamValidate($type, $str): bool
     {
         $list = self::getSsMethod($type);
@@ -352,9 +328,6 @@ final class Tools
         }
     }
 
-    /**
-     * @param $input
-     */
     public static function isEmail($input): bool
     {
         if (! filter_var($input, FILTER_VALIDATE_EMAIL)) {
@@ -364,9 +337,6 @@ final class Tools
         return true;
     }
 
-    /**
-     * @param $input
-     */
     public static function isIPv4($input): bool
     {
         if (! filter_var($input, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
@@ -376,9 +346,6 @@ final class Tools
         return true;
     }
 
-    /**
-     * @param $input
-     */
     public static function isIPv6($input): bool
     {
         if (! filter_var($input, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
@@ -388,9 +355,6 @@ final class Tools
         return true;
     }
 
-    /**
-     * @param $input
-     */
     public static function isInt($input): bool
     {
         if (! filter_var($input, FILTER_VALIDATE_INT)) {
@@ -403,8 +367,6 @@ final class Tools
     /**
      * 判断是否 JSON
      * TODO: Remove this function when PHP 8.3 is minimum requirement and replace it with native function
-     *
-     * @param string $string
      */
     public static function isJson(string $string): bool
     {

+ 0 - 1
tests/App/Services/DBTest.php

@@ -15,7 +15,6 @@ class DBTest extends TestCase
     {
         // Scenario 1: enable_db_rw_split is true
         $_ENV['enable_db_rw_split'] = true;
-        $_ENV['db_driver'] = 'mysql';
         $_ENV['read_db_hosts'] = 'localhost';
         $_ENV['write_db_host'] = 'localhost';
         $_ENV['db_database'] = 'test_db';