浏览代码

Merge pull request #2225 from sspanel-uim/dev

Dev 20231101
M1Screw 1 年之前
父节点
当前提交
bac995e23f
共有 67 个文件被更改,包括 841 次插入851 次删除
  1. 1 1
      app/predefine.php
  2. 115 101
      app/routes.php
  3. 27 26
      composer.json
  4. 148 219
      composer.lock
  5. 1 1
      config/.config.example.php
  6. 3 0
      resources/views/tabler/admin/coupon.tpl
  7. 2 2
      resources/views/tabler/admin/header.tpl
  8. 1 1
      resources/views/tabler/admin/log/money.tpl
  9. 1 1
      resources/views/tabler/admin/log/traffic.tpl
  10. 1 1
      resources/views/tabler/admin/node/edit.tpl
  11. 1 1
      resources/views/tabler/admin/setting/email.tpl
  12. 3 3
      resources/views/tabler/admin/setting/im.tpl
  13. 3 3
      resources/views/tabler/admin/system.tpl
  14. 1 1
      resources/views/tabler/user/header.tpl
  15. 1 1
      resources/views/tabler/user/invoice/view.tpl
  16. 1 1
      resources/views/tabler/user/order/create.tpl
  17. 1 0
      src/Controllers/Admin/AnnController.php
  18. 1 1
      src/Controllers/Admin/CouponController.php
  19. 67 0
      src/Controllers/Admin/DetectBanController.php
  20. 0 236
      src/Controllers/Admin/DetectController.php
  21. 72 0
      src/Controllers/Admin/DetectLogController.php
  22. 126 0
      src/Controllers/Admin/DetectRuleController.php
  23. 1 0
      src/Controllers/Admin/DocsController.php
  24. 1 0
      src/Controllers/Admin/GiftCardController.php
  25. 1 1
      src/Controllers/Admin/InviteController.php
  26. 11 11
      src/Controllers/Admin/MoneyLogController.php
  27. 51 68
      src/Controllers/Admin/NodeController.php
  28. 12 12
      src/Controllers/Admin/PaylistController.php
  29. 2 2
      src/Controllers/Admin/Setting/BillingController.php
  30. 2 2
      src/Controllers/Admin/Setting/CaptchaController.php
  31. 2 2
      src/Controllers/Admin/Setting/CronController.php
  32. 2 2
      src/Controllers/Admin/Setting/EmailController.php
  33. 2 2
      src/Controllers/Admin/Setting/FeatureController.php
  34. 2 2
      src/Controllers/Admin/Setting/ImController.php
  35. 2 2
      src/Controllers/Admin/Setting/RefController.php
  36. 2 2
      src/Controllers/Admin/Setting/RegController.php
  37. 3 3
      src/Controllers/Admin/Setting/SubController.php
  38. 2 2
      src/Controllers/Admin/Setting/SupportController.php
  39. 12 12
      src/Controllers/Admin/SubLogController.php
  40. 3 0
      src/Controllers/Admin/SystemController.php
  41. 12 12
      src/Controllers/Admin/TicketController.php
  42. 10 9
      src/Controllers/Admin/TrafficLogController.php
  43. 1 1
      src/Controllers/Admin/UserController.php
  44. 37 0
      src/Controllers/User/DetectLogController.php
  45. 1 1
      src/Controllers/User/DetectRuleController.php
  46. 1 1
      src/Controllers/User/InfoController.php
  47. 1 1
      src/Controllers/User/InvoiceController.php
  48. 0 62
      src/Controllers/User/LogController.php
  49. 1 1
      src/Controllers/User/MoneyController.php
  50. 1 1
      src/Controllers/User/OrderController.php
  51. 1 1
      src/Controllers/User/ProductController.php
  52. 1 1
      src/Controllers/User/ServerController.php
  53. 34 0
      src/Controllers/User/SubLogController.php
  54. 4 4
      src/Controllers/User/TicketController.php
  55. 1 1
      src/Middleware/AdminApi.php
  56. 2 2
      src/Middleware/Auth.php
  57. 1 1
      src/Middleware/NodeApi.php
  58. 25 0
      src/Middleware/UserApi.php
  59. 1 1
      src/Models/DetectRule.php
  60. 0 3
      src/Services/Auth.php
  61. 2 2
      src/Services/Bot/Telegram/Callback.php
  62. 1 7
      src/Services/Config.php
  63. 1 1
      src/Services/Exchange.php
  64. 4 4
      src/Services/GeoIP2.php
  65. 4 5
      src/Services/Subscribe/Clash.php
  66. 1 1
      src/Utils/Tools.php
  67. 3 3
      tests/App/Services/ConfigTest.php

+ 1 - 1
app/predefine.php

@@ -7,4 +7,4 @@ declare(strict_types=1);
  */
 
 const BASE_PATH = __DIR__ . '/..';
-const VERSION = '2023.5';
+const VERSION = '2023.6';

+ 115 - 101
app/routes.php

@@ -22,6 +22,10 @@ return static function (Slim\App $app): void {
     // OAuth
     $app->post('/oauth/{type}', App\Controllers\OAuthController::class . ':index');
     $app->get('/oauth/{type}', App\Controllers\OAuthController::class . ':index');
+    // 传统订阅(SS/V2Ray/Trojan etc.)
+    $app->get('/link/{token}', App\Controllers\SubController::class . ':getTraditionalSubContent');
+    // 通用订阅(Json/Clash/SIP008)
+    $app->get('/sub/{token}/{subtype}', App\Controllers\SubController::class . ':getUniversalSubContent');
     // User Center
     $app->group('/user', static function (RouteCollectorProxy $group): void {
         $group->get('', App\Controllers\UserController::class . ':index');
@@ -32,25 +36,27 @@ return static function (Slim\App $app): void {
         $group->get('/announcement', App\Controllers\UserController::class . ':announcement');
         // 文档
         $group->get('/docs', App\Controllers\User\DocsController::class . ':index');
-        $group->get('/docs/{id}/view', App\Controllers\User\DocsController::class . ':detail');
+        $group->get('/docs/{id:[0-9]+}/view', App\Controllers\User\DocsController::class . ':detail');
         // 个人资料
         $group->get('/profile', App\Controllers\UserController::class . ':profile');
         $group->get('/invite', App\Controllers\UserController::class . ':invite');
         // 封禁
         $group->get('/banned', App\Controllers\UserController::class . ':banned');
         // 节点
-        $group->get('/server', App\Controllers\User\ServerController::class . ':server');
+        $group->get('/server', App\Controllers\User\ServerController::class . ':index');
         // 动态倍率
         $group->get('/rate', App\Controllers\User\RateController::class . ':index');
         $group->post('/rate', App\Controllers\User\RateController::class . ':ajax');
-        // 审计
-        $group->get('/detect', App\Controllers\User\DetectController::class . ':index');
+        // 审计规则
+        $group->get('/detect', App\Controllers\User\DetectRuleController::class . ':index');
+        // 审计记录
+        $group->get('/detect/log', App\Controllers\User\DetectLogController::class . ':index');
         // 工单
-        $group->get('/ticket', App\Controllers\User\TicketController::class . ':ticket');
-        $group->get('/ticket/create', App\Controllers\User\TicketController::class . ':ticketCreate');
-        $group->post('/ticket', App\Controllers\User\TicketController::class . ':ticketAdd');
-        $group->get('/ticket/{id}/view', App\Controllers\User\TicketController::class . ':ticketView');
-        $group->put('/ticket/{id}', App\Controllers\User\TicketController::class . ':ticketUpdate');
+        $group->get('/ticket', App\Controllers\User\TicketController::class . ':index');
+        $group->get('/ticket/create', App\Controllers\User\TicketController::class . ':create');
+        $group->post('/ticket', App\Controllers\User\TicketController::class . ':add');
+        $group->get('/ticket/{id:[0-9]+}/view', App\Controllers\User\TicketController::class . ':detail');
+        $group->put('/ticket/{id:[0-9]+}', App\Controllers\User\TicketController::class . ':update');
         // 资料编辑
         $group->get('/edit', App\Controllers\User\InfoController::class . ':index');
         $group->post('/email', App\Controllers\User\InfoController::class . ':updateEmail');
@@ -68,32 +74,28 @@ return static function (Slim\App $app): void {
         $group->post('/kill', App\Controllers\User\InfoController::class . ':sendToGulag');
         // 发送验证邮件
         $group->post('/send', App\Controllers\AuthController::class . ':sendVerify');
-        // 登出
-        $group->get('/logout', App\Controllers\UserController::class . ':logout');
         // MFA
         $group->post('/ga_check', App\Controllers\User\MFAController::class . ':checkGa');
         $group->post('/ga_set', App\Controllers\User\MFAController::class . ':setGa');
         $group->post('/ga_reset', App\Controllers\User\MFAController::class . ':resetGa');
         // 深色模式切换
         $group->post('/switch_theme_mode', App\Controllers\UserController::class . ':switchThemeMode');
-        // 记录
-        $group->get('/subscribe/log', App\Controllers\User\LogController::class . ':subscribe');
-        $group->get('/detect/log', App\Controllers\User\LogController::class . ':detect');
+        // 订阅记录
+        $group->get('/subscribe', App\Controllers\User\SubLogController::class . ':index');
         // 账户余额
-        $group->get('/money', App\Controllers\User\MoneyController::class . ':money');
-        // 礼品卡兑换
+        $group->get('/money', App\Controllers\User\MoneyController::class . ':index');
         $group->post('/giftcard', App\Controllers\User\MoneyController::class . ':applyGiftCard');
         // 产品页面
-        $group->get('/product', App\Controllers\User\ProductController::class . ':product');
+        $group->get('/product', App\Controllers\User\ProductController::class . ':index');
         // 订单页面
-        $group->get('/order', App\Controllers\User\OrderController::class . ':order');
+        $group->get('/order', App\Controllers\User\OrderController::class . ':index');
         $group->get('/order/create', App\Controllers\User\OrderController::class . ':create');
         $group->post('/order/create', App\Controllers\User\OrderController::class . ':process');
-        $group->get('/order/{id}/view', App\Controllers\User\OrderController::class . ':detail');
+        $group->get('/order/{id:[0-9]+}/view', App\Controllers\User\OrderController::class . ':detail');
         $group->post('/order/ajax', App\Controllers\User\OrderController::class . ':ajax');
         // 账单页面
-        $group->get('/invoice', App\Controllers\User\InvoiceController::class . ':invoice');
-        $group->get('/invoice/{id}/view', App\Controllers\User\InvoiceController::class . ':detail');
+        $group->get('/invoice', App\Controllers\User\InvoiceController::class . ':index');
+        $group->get('/invoice/{id:[0-9]+}/view', App\Controllers\User\InvoiceController::class . ':detail');
         $group->post('/invoice/pay_balance', App\Controllers\User\InvoiceController::class . ':payBalance');
         $group->post('/invoice/ajax', App\Controllers\User\InvoiceController::class . ':ajax');
         // 新优惠码系统
@@ -104,6 +106,8 @@ return static function (Slim\App $app): void {
         $group->get('/payment/return/{type}', App\Services\Payment::class . ':returnHTML');
         // Get Clients
         $group->get('/clients/{name}', App\Controllers\User\ClientController::class . ':getClients');
+        // 登出
+        $group->get('/logout', App\Controllers\UserController::class . ':logout');
     })->add(new Auth());
 
     $app->group('/payment', static function (RouteCollectorProxy $group): void {
@@ -138,61 +142,63 @@ return static function (Slim\App $app): void {
         $group->get('/node', App\Controllers\Admin\NodeController::class . ':index');
         $group->get('/node/create', App\Controllers\Admin\NodeController::class . ':create');
         $group->post('/node', App\Controllers\Admin\NodeController::class . ':add');
-        $group->get('/node/{id}/edit', App\Controllers\Admin\NodeController::class . ':edit');
-        $group->post('/node/{id}/password_reset', App\Controllers\Admin\NodeController::class . ':resetNodePassword');
-        $group->post('/node/{id}/copy', App\Controllers\Admin\NodeController::class . ':copy');
-        $group->put('/node/{id}', App\Controllers\Admin\NodeController::class . ':update');
-        $group->delete('/node/{id}', App\Controllers\Admin\NodeController::class . ':delete');
+        $group->get('/node/{id:[0-9]+}/edit', App\Controllers\Admin\NodeController::class . ':edit');
+        $group->post('/node/{id:[0-9]+}/reset', App\Controllers\Admin\NodeController::class . ':reset');
+        $group->post('/node/{id:[0-9]+}/copy', App\Controllers\Admin\NodeController::class . ':copy');
+        $group->put('/node/{id:[0-9]+}', App\Controllers\Admin\NodeController::class . ':update');
+        $group->delete('/node/{id:[0-9]+}', App\Controllers\Admin\NodeController::class . ':delete');
         $group->post('/node/ajax', App\Controllers\Admin\NodeController::class . ':ajax');
         // Ticket
         $group->get('/ticket', App\Controllers\Admin\TicketController::class . ':index');
         $group->post('/ticket', App\Controllers\Admin\TicketController::class . ':add');
-        $group->get('/ticket/{id}/view', App\Controllers\Admin\TicketController::class . ':ticketView');
-        $group->put('/ticket/{id}/close', App\Controllers\Admin\TicketController::class . ':close');
-        $group->put('/ticket/{id}', App\Controllers\Admin\TicketController::class . ':update');
-        $group->put('/ticket/{id}/ai', App\Controllers\Admin\TicketController::class . ':updateAI');
-        $group->delete('/ticket/{id}', App\Controllers\Admin\TicketController::class . ':delete');
+        $group->get('/ticket/{id:[0-9]+}/view', App\Controllers\Admin\TicketController::class . ':detail');
+        $group->put('/ticket/{id:[0-9]+}/close', App\Controllers\Admin\TicketController::class . ':close');
+        $group->put('/ticket/{id:[0-9]+}', App\Controllers\Admin\TicketController::class . ':update');
+        $group->put('/ticket/{id:[0-9]+}/ai', App\Controllers\Admin\TicketController::class . ':updateAI');
+        $group->delete('/ticket/{id:[0-9]+}', App\Controllers\Admin\TicketController::class . ':delete');
         $group->post('/ticket/ajax', App\Controllers\Admin\TicketController::class . ':ajax');
         // Ann
         $group->get('/announcement', App\Controllers\Admin\AnnController::class . ':index');
         $group->get('/announcement/create', App\Controllers\Admin\AnnController::class . ':create');
         $group->post('/announcement', App\Controllers\Admin\AnnController::class . ':add');
-        $group->get('/announcement/{id}/edit', App\Controllers\Admin\AnnController::class . ':edit');
-        $group->put('/announcement/{id}', App\Controllers\Admin\AnnController::class . ':update');
-        $group->delete('/announcement/{id}', App\Controllers\Admin\AnnController::class . ':delete');
+        $group->get('/announcement/{id:[0-9]+}/edit', App\Controllers\Admin\AnnController::class . ':edit');
+        $group->put('/announcement/{id:[0-9]+}', App\Controllers\Admin\AnnController::class . ':update');
+        $group->delete('/announcement/{id:[0-9]+}', App\Controllers\Admin\AnnController::class . ':delete');
         $group->post('/announcement/ajax', App\Controllers\Admin\AnnController::class . ':ajax');
         // Docs
         $group->get('/docs', App\Controllers\Admin\DocsController::class . ':index');
         $group->get('/docs/create', App\Controllers\Admin\DocsController::class . ':create');
         $group->post('/docs', App\Controllers\Admin\DocsController::class . ':add');
         $group->post('/docs/generate', App\Controllers\Admin\DocsController::class . ':generate');
-        $group->get('/docs/{id}/edit', App\Controllers\Admin\DocsController::class . ':edit');
-        $group->put('/docs/{id}', App\Controllers\Admin\DocsController::class . ':update');
-        $group->delete('/docs/{id}', App\Controllers\Admin\DocsController::class . ':delete');
+        $group->get('/docs/{id:[0-9]+}/edit', App\Controllers\Admin\DocsController::class . ':edit');
+        $group->put('/docs/{id:[0-9]+}', App\Controllers\Admin\DocsController::class . ':update');
+        $group->delete('/docs/{id:[0-9]+}', App\Controllers\Admin\DocsController::class . ':delete');
         $group->post('/docs/ajax', App\Controllers\Admin\DocsController::class . ':ajax');
         // 审计
-        $group->get('/detect', App\Controllers\Admin\DetectController::class . ':detect');
-        $group->get('/detect/create', App\Controllers\Admin\DetectController::class . ':create');
-        $group->post('/detect/add', App\Controllers\Admin\DetectController::class . ':add');
-        $group->delete('/detect/{id}', App\Controllers\Admin\DetectController::class . ':delete');
-        $group->post('/detect/ajax', App\Controllers\Admin\DetectController::class . ':ajaxRule');
-        $group->get('/detect/log', App\Controllers\Admin\DetectController::class . ':log');
-        $group->post('/detect/log/ajax', App\Controllers\Admin\DetectController::class . ':ajaxLog');
-        $group->get('/detect/ban', App\Controllers\Admin\DetectController::class . ':ban');
-        $group->post('/detect/ban/ajax', App\Controllers\Admin\DetectController::class . ':ajaxBan');
+        $group->get('/detect', App\Controllers\Admin\DetectRuleController::class . ':index');
+        $group->get('/detect/create', App\Controllers\Admin\DetectRuleController::class . ':create');
+        $group->post('/detect/add', App\Controllers\Admin\DetectRuleController::class . ':add');
+        $group->delete('/detect/{id:[0-9]+}', App\Controllers\Admin\DetectRuleController::class . ':delete');
+        $group->post('/detect/ajax', App\Controllers\Admin\DetectRuleController::class . ':ajax');
+        // 审计触发日志
+        $group->get('/detect/log', App\Controllers\Admin\DetectLogController::class . ':index');
+        $group->post('/detect/log/ajax', App\Controllers\Admin\DetectLogController::class . ':ajax');
+        // 审计封禁日志
+        $group->get('/detect/ban', App\Controllers\Admin\DetectBanController::class . ':index');
+        $group->post('/detect/ban/ajax', App\Controllers\Admin\DetectBanController::class . ':ajax');
         // User
         $group->get('/user', App\Controllers\Admin\UserController::class . ':index');
-        $group->get('/user/{id}/edit', App\Controllers\Admin\UserController::class . ':edit');
-        $group->put('/user/{id}', App\Controllers\Admin\UserController::class . ':update');
-        $group->post('/user/create', App\Controllers\Admin\UserController::class . ':createNewUser');
+        $group->get('/user/{id:[0-9]+}/edit', App\Controllers\Admin\UserController::class . ':edit');
+        $group->put('/user/{id:[0-9]+}', App\Controllers\Admin\UserController::class . ':update');
+        $group->post('/user/create', App\Controllers\Admin\UserController::class . ':create');
         $group->delete('/user/{id}', App\Controllers\Admin\UserController::class . ':delete');
         $group->post('/user/ajax', App\Controllers\Admin\UserController::class . ':ajax');
         // Coupon
         $group->get('/coupon', App\Controllers\Admin\CouponController::class . ':index');
         $group->post('/coupon', App\Controllers\Admin\CouponController::class . ':add');
         $group->post('/coupon/ajax', App\Controllers\Admin\CouponController::class . ':ajax');
-        $group->delete('/coupon/{id}', App\Controllers\Admin\CouponController::class . ':delete');
-        $group->post('/coupon/{id}/disable', App\Controllers\Admin\CouponController::class . ':disable');
+        $group->delete('/coupon/{id:[0-9]+}', App\Controllers\Admin\CouponController::class . ':delete');
+        $group->post('/coupon/{id:[0-9]+}/disable', App\Controllers\Admin\CouponController::class . ':disable');
         // 登录日志
         $group->get('/login', App\Controllers\Admin\LoginLogController::class . ':index');
         $group->post('/login/ajax', App\Controllers\Admin\LoginLogController::class . ':ajax');
@@ -200,19 +206,19 @@ return static function (Slim\App $app): void {
         $group->get('/online', App\Controllers\Admin\OnlineLogController::class . ':index');
         $group->post('/online/ajax', App\Controllers\Admin\OnlineLogController::class . ':ajax');
         // 订阅日志
-        $group->get('/subscribe', App\Controllers\Admin\SubscribeLogController::class . ':index');
-        $group->post('/subscribe/ajax', App\Controllers\Admin\SubscribeLogController::class . ':ajax');
+        $group->get('/subscribe', App\Controllers\Admin\SubLogController::class . ':index');
+        $group->post('/subscribe/ajax', App\Controllers\Admin\SubLogController::class . ':ajax');
         // 邀请日志
-        $group->get('/invite', App\Controllers\Admin\InviteController::class . ':invite');
+        $group->get('/invite', App\Controllers\Admin\InviteController::class . ':index');
         $group->post('/invite/update_invite', App\Controllers\Admin\InviteController::class . ':update');
         $group->post('/invite/add_invite', App\Controllers\Admin\InviteController::class . ':add');
         $group->post('/invite/ajax', App\Controllers\Admin\InviteController::class . ':ajax');
         // 流量日志
-        $group->get('/trafficlog', App\Controllers\Admin\TrafficLogController::class . ':index');
-        $group->post('/trafficlog/ajax', App\Controllers\Admin\TrafficLogController::class . ':ajax');
+        $group->get('/traffic', App\Controllers\Admin\TrafficLogController::class . ':index');
+        $group->post('/traffic/ajax', App\Controllers\Admin\TrafficLogController::class . ':ajax');
         // 用户余额日志
-        $group->get('/moneylog', App\Controllers\Admin\MoneyLogController::class . ':index');
-        $group->post('/moneylog/ajax', App\Controllers\Admin\MoneyLogController::class . ':ajax');
+        $group->get('/money', App\Controllers\Admin\MoneyLogController::class . ':index');
+        $group->post('/money/ajax', App\Controllers\Admin\MoneyLogController::class . ':ajax');
         // 支付网关日志
         $group->get('/gateway', App\Controllers\Admin\PaylistController::class . ':index');
         $group->post('/gateway/ajax', App\Controllers\Admin\PaylistController::class . ':ajax');
@@ -220,30 +226,31 @@ return static function (Slim\App $app): void {
         $group->get('/system', App\Controllers\Admin\SystemController::class . ':index');
         $group->post('/system/check_update', App\Controllers\Admin\SystemController::class . ':checkUpdate');
         // 设置中心
-        $group->get('/setting/billing', App\Controllers\Admin\Setting\BillingController::class . ':billing');
-        $group->post('/setting/billing', App\Controllers\Admin\Setting\BillingController::class . ':saveBilling');
-        $group->get('/setting/captcha', App\Controllers\Admin\Setting\CaptchaController::class . ':captcha');
-        $group->post('/setting/captcha', App\Controllers\Admin\Setting\CaptchaController::class . ':saveCaptcha');
-        $group->get('/setting/cron', App\Controllers\Admin\Setting\CronController::class . ':cron');
-        $group->post('/setting/cron', App\Controllers\Admin\Setting\CronController::class . ':saveCron');
-        $group->get('/setting/email', App\Controllers\Admin\Setting\EmailController::class . ':email');
-        $group->post('/setting/email', App\Controllers\Admin\Setting\EmailController::class . ':saveEmail');
-        $group->get('/setting/feature', App\Controllers\Admin\Setting\FeatureController::class . ':feature');
-        $group->post('/setting/feature', App\Controllers\Admin\Setting\FeatureController::class . ':saveFeature');
-        $group->get('/setting/im', App\Controllers\Admin\Setting\ImController::class . ':im');
-        $group->post('/setting/im', App\Controllers\Admin\Setting\ImController::class . ':saveIm');
-        $group->get('/setting/ref', App\Controllers\Admin\Setting\RefController::class . ':ref');
-        $group->post('/setting/ref', App\Controllers\Admin\Setting\RefController::class . ':saveRef');
-        $group->get('/setting/reg', App\Controllers\Admin\Setting\RegController::class . ':reg');
-        $group->post('/setting/reg', App\Controllers\Admin\Setting\RegController::class . ':saveReg');
-        $group->get('/setting/sub', App\Controllers\Admin\Setting\SubscribeController::class . ':sub');
-        $group->post('/setting/sub', App\Controllers\Admin\Setting\SubscribeController::class . ':saveSub');
-        $group->get('/setting/support', App\Controllers\Admin\Setting\SupportController::class . ':support');
-        $group->post('/setting/support', App\Controllers\Admin\Setting\SupportController::class . ':saveSupport');
-        $group->post('/setting/test_email', App\Controllers\Admin\Setting\EmailController::class . ':testEmail');
-        $group->post('/setting/test_telegram', App\Controllers\Admin\Setting\ImController::class . ':testTelegram');
-        $group->post('/setting/test_discord', App\Controllers\Admin\Setting\ImController::class . ':testDiscord');
-        $group->post('/setting/test_slack', App\Controllers\Admin\Setting\ImController::class . ':testSlack');
+        $group->get('/setting/billing', App\Controllers\Admin\Setting\BillingController::class . ':index');
+        $group->post('/setting/billing', App\Controllers\Admin\Setting\BillingController::class . ':save');
+        $group->get('/setting/captcha', App\Controllers\Admin\Setting\CaptchaController::class . ':index');
+        $group->post('/setting/captcha', App\Controllers\Admin\Setting\CaptchaController::class . ':save');
+        $group->get('/setting/cron', App\Controllers\Admin\Setting\CronController::class . ':index');
+        $group->post('/setting/cron', App\Controllers\Admin\Setting\CronController::class . ':save');
+        $group->get('/setting/email', App\Controllers\Admin\Setting\EmailController::class . ':index');
+        $group->post('/setting/email', App\Controllers\Admin\Setting\EmailController::class . ':save');
+        $group->get('/setting/feature', App\Controllers\Admin\Setting\FeatureController::class . ':index');
+        $group->post('/setting/feature', App\Controllers\Admin\Setting\FeatureController::class . ':save');
+        $group->get('/setting/im', App\Controllers\Admin\Setting\ImController::class . ':index');
+        $group->post('/setting/im', App\Controllers\Admin\Setting\ImController::class . ':save');
+        $group->get('/setting/ref', App\Controllers\Admin\Setting\RefController::class . ':index');
+        $group->post('/setting/ref', App\Controllers\Admin\Setting\RefController::class . ':save');
+        $group->get('/setting/reg', App\Controllers\Admin\Setting\RegController::class . ':index');
+        $group->post('/setting/reg', App\Controllers\Admin\Setting\RegController::class . ':save');
+        $group->get('/setting/sub', App\Controllers\Admin\Setting\SubController::class . ':index');
+        $group->post('/setting/sub', App\Controllers\Admin\Setting\SubController::class . ':save');
+        $group->get('/setting/support', App\Controllers\Admin\Setting\SupportController::class . ':index');
+        $group->post('/setting/support', App\Controllers\Admin\Setting\SupportController::class . ':save');
+        // 设置测试
+        $group->post('/setting/test/email', App\Controllers\Admin\Setting\EmailController::class . ':testEmail');
+        $group->post('/setting/test/telegram', App\Controllers\Admin\Setting\ImController::class . ':testTelegram');
+        $group->post('/setting/test/discord', App\Controllers\Admin\Setting\ImController::class . ':testDiscord');
+        $group->post('/setting/test/slack', App\Controllers\Admin\Setting\ImController::class . ':testSlack');
         // 礼品卡
         $group->get('/giftcard', App\Controllers\Admin\GiftCardController::class . ':index');
         $group->post('/giftcard', App\Controllers\Admin\GiftCardController::class . ':add');
@@ -253,31 +260,43 @@ return static function (Slim\App $app): void {
         $group->get('/product', App\Controllers\Admin\ProductController::class . ':index');
         $group->get('/product/create', App\Controllers\Admin\ProductController::class . ':create');
         $group->post('/product', App\Controllers\Admin\ProductController::class . ':add');
-        $group->get('/product/{id}/edit', App\Controllers\Admin\ProductController::class . ':edit');
-        $group->post('/product/{id}/copy', App\Controllers\Admin\ProductController::class . ':copy');
-        $group->put('/product/{id}', App\Controllers\Admin\ProductController::class . ':update');
-        $group->delete('/product/{id}', App\Controllers\Admin\ProductController::class . ':delete');
+        $group->get('/product/{id:[0-9]+}/edit', App\Controllers\Admin\ProductController::class . ':edit');
+        $group->post('/product/{id:[0-9]+}/copy', App\Controllers\Admin\ProductController::class . ':copy');
+        $group->put('/product/{id:[0-9]+}', App\Controllers\Admin\ProductController::class . ':update');
+        $group->delete('/product/{id:[0-9]+}', App\Controllers\Admin\ProductController::class . ':delete');
         $group->post('/product/ajax', App\Controllers\Admin\ProductController::class . ':ajax');
         // 订单
         $group->get('/order', App\Controllers\Admin\OrderController::class . ':index');
-        $group->get('/order/{id}/view', App\Controllers\Admin\OrderController::class . ':detail');
-        $group->post('/order/{id}/cancel', App\Controllers\Admin\OrderController::class . ':cancel');
+        $group->get('/order/{id:[0-9]+}/view', App\Controllers\Admin\OrderController::class . ':detail');
+        $group->post('/order/{id:[0-9]+}/cancel', App\Controllers\Admin\OrderController::class . ':cancel');
         $group->delete('/order/{id}', App\Controllers\Admin\OrderController::class . ':delete');
         $group->post('/order/ajax', App\Controllers\Admin\OrderController::class . ':ajax');
         // 账单
         $group->get('/invoice', App\Controllers\Admin\InvoiceController::class . ':index');
-        $group->get('/invoice/{id}/view', App\Controllers\Admin\InvoiceController::class . ':detail');
-        $group->post('/invoice/{id}/mark_paid', App\Controllers\Admin\InvoiceController::class . ':markPaid');
+        $group->get('/invoice/{id:[0-9]+}/view', App\Controllers\Admin\InvoiceController::class . ':detail');
+        $group->post('/invoice/{id:[0-9]+}/mark_paid', App\Controllers\Admin\InvoiceController::class . ':markPaid');
         $group->post('/invoice/ajax', App\Controllers\Admin\InvoiceController::class . ':ajax');
     })->add(new Admin());
 
-    //$app->group('/admin/api', function (RouteCollectorProxy $group): void {
-    //    $group->post('/{action}', App\Controllers\Api\AdminApiController::class . ':actionHandler');
-    //})->add(new AdminApiToken());
+    // Admin CLI API
+    //$app->group('/admin/api/v1', function (RouteCollectorProxy $group): void {
+    //    $group->post('/{action}', App\Controllers\Api\AdminApiV1Controller::class . ':actionHandler');
+    //})->add(new AdminApi());
+
+    // User CLI API
+    //$app->group('/user/api/v1', function (RouteCollectorProxy $group): void {
+    //    $group->post('/{action}', App\Controllers\Api\UserApiV1Controller::class . ':actionHandler');
+    //})->add(new UserApi());
 
-    //$app->group('/user/api', function (RouteCollectorProxy $group): void {
-    //    $group->post('/{action}', App\Controllers\Api\UserApiController::class . ':actionHandler');
-    //})->add(new UserApiToken());
+    // WebAPI V2(Aka Node API V1)
+    //$app->group('/node/api/v1', function (RouteCollectorProxy $group): void {
+    //    $group->get('/info', App\Controllers\Api\NodeApiV1Controller::class . ':info');
+    //    $group->get('/user', App\Controllers\Api\NodeApiV1Controller::class . ':user');
+    //    $group->get('/detect_rule', App\Controllers\Api\NodeApiV1Controller::class . ':detectRule');
+    //    $group->post('/user/traffic', App\Controllers\Api\NodeApiV1Controller::class . ':userTraffic');
+    //    $group->post('/user/online_ip', App\Controllers\Api\NodeApiV1Controller::class . ':userOnlineIp');
+    //    $group->post('/user/detect_log', App\Controllers\Api\NodeApiV1Controller::class . ':userDetectLog');
+    //})->add(new WebApi());
 
     // WebAPI
     $app->group('/mod_mu', static function (RouteCollectorProxy $group): void {
@@ -292,9 +311,4 @@ return static function (Slim\App $app): void {
         $group->get('/func/detect_rules', App\Controllers\WebAPI\FuncController::class . ':getDetectRules');
         $group->get('/func/ping', App\Controllers\WebAPI\FuncController::class . ':ping');
     })->add(new NodeToken());
-
-    // 传统订阅(SS/V2Ray/Trojan etc.)
-    $app->get('/link/{token}', App\Controllers\SubController::class . ':getTraditionalSubContent');
-    // 通用订阅(Json/Clash/SIP008)
-    $app->get('/sub/{token}/{subtype}', App\Controllers\SubController::class . ':getUniversalSubContent');
 };

+ 27 - 26
composer.json

@@ -12,36 +12,36 @@
         "ext-posix": "*",
         "ext-redis": "*",
         "ext-xml": "*",
+        "ext-yaml": "*",
         "ext-zip": "*",
         "anankke/omnipay-alipay": "^3.1.3",
-        "aws/aws-sdk-php": "^3",
-        "cloudflare/sdk": "^1",
-        "geoip2/geoip2": "~2.0",
-        "guzzlehttp/guzzle": "^7.4",
-        "guzzlehttp/psr7": "^2.4",
-        "illuminate/database": "^10.0",
-        "illuminate/pagination": "^10.0",
-        "irazasyed/telegram-bot-sdk": "^3",
+        "aws/aws-sdk-php": "^3.283.14",
+        "cloudflare/sdk": "^1.3",
+        "geoip2/geoip2": "^2.13",
+        "guzzlehttp/guzzle": "^7.8",
+        "guzzlehttp/psr7": "^2.6.1",
+        "illuminate/database": "^10.29",
+        "illuminate/pagination": "^10.29",
+        "irazasyed/telegram-bot-sdk": "^3.13",
         "lcobucci/jwt": "^5.0",
         "league/omnipay": "^3.2.1",
-        "mailgun/mailgun-php": "^3",
+        "mailgun/mailgun-php": "^3.6.1",
         "nikolaposa/rate-limit": "^3.0",
-        "openai-php/client": "^0.7",
-        "ozdemir/datatables": "^2",
-        "phpmailer/phpmailer": "^6",
+        "openai-php/client": "^0.7.4",
+        "ozdemir/datatables": "^2.3.7",
+        "phpmailer/phpmailer": "^6.8.1",
         "postal/postal": "^2",
-        "ramsey/uuid": "^4",
-        "sendgrid/sendgrid": "^8",
-        "sentry/sdk": "^3.3",
+        "ramsey/uuid": "^4.7.4",
+        "sendgrid/sendgrid": "^8.0.1",
+        "sentry/sdk": "^3.5",
         "slim/http": "^1.3",
-        "slim/slim": "^4.11",
-        "smarty/smarty": "^4",
-        "srmklive/paypal": "~3.0",
-        "stripe/stripe-php": "^12",
-        "symfony/yaml": "^6",
-        "tronovav/geoip2-update": "^2.1",
+        "slim/slim": "^4.12",
+        "smarty/smarty": "^4.3.4",
+        "srmklive/paypal": "^3.0.26",
+        "stripe/stripe-php": "^12.8",
+        "tronovav/geoip2-update": "^2.3.1",
         "vectorface/googleauthenticator": "^3.0",
-        "voku/anti-xss": "^4"
+        "voku/anti-xss": "^4.1.42"
     },
     "autoload": {
         "psr-4": {
@@ -58,14 +58,15 @@
     },
     "require-dev": {
         "nunomaduro/phpinsights": "*",
-        "phpunit/phpunit": "^10.4"
+        "phpunit/phpunit": "^10.4.2"
     },
     "scripts": {
         "update-dev-windows": [
-            "composer update --ignore-platform-req=ext-posix --ignore-platform-req=ext-redis"
+            "composer update --ignore-platform-req=ext-posix --ignore-platform-req=ext-redis --ignore-platform-req=ext-yaml"
         ],
         "install-dev-windows": [
-            "composer instal --ignore-platform-req=ext-posix --ignore-platform-req=ext-redis"
+            "composer instal --ignore-platform-req=ext-posix --ignore-platform-req=ext-redis --ignore-platform-req=ext-yaml"
         ]
-    }
+    },
+    "type": "project"
 }

+ 148 - 219
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": "a968baf5a7521ed5df3bc6127e9230c7",
+    "content-hash": "58dc2315c9baefadd2dcc29e3d794ac5",
     "packages": [
         {
             "name": "anankke/omnipay-alipay",
@@ -123,16 +123,16 @@
         },
         {
             "name": "aws/aws-sdk-php",
-            "version": "3.283.13",
+            "version": "3.283.16",
             "source": {
                 "type": "git",
                 "url": "https://github.com/aws/aws-sdk-php.git",
-                "reference": "853eecfb21e8d623fa1b32e597b0a75912e8a404"
+                "reference": "cb629771356d6a4d12bb030a072b832044440672"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/853eecfb21e8d623fa1b32e597b0a75912e8a404",
-                "reference": "853eecfb21e8d623fa1b32e597b0a75912e8a404",
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/cb629771356d6a4d12bb030a072b832044440672",
+                "reference": "cb629771356d6a4d12bb030a072b832044440672",
                 "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.283.13"
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.283.16"
             },
-            "time": "2023-10-26T18:14:40+00:00"
+            "time": "2023-10-31T18:21:09+00:00"
         },
         {
             "name": "bacon/bacon-qr-code",
@@ -1237,7 +1237,7 @@
         },
         {
             "name": "illuminate/collections",
-            "version": "v10.29.0",
+            "version": "v10.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/collections.git",
@@ -1292,7 +1292,7 @@
         },
         {
             "name": "illuminate/conditionable",
-            "version": "v10.29.0",
+            "version": "v10.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/conditionable.git",
@@ -1338,7 +1338,7 @@
         },
         {
             "name": "illuminate/container",
-            "version": "v10.29.0",
+            "version": "v10.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/container.git",
@@ -1389,16 +1389,16 @@
         },
         {
             "name": "illuminate/contracts",
-            "version": "v10.29.0",
+            "version": "v10.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/contracts.git",
-                "reference": "6c39fba7b2311e28f5c6ac7d729e3d49a2a98406"
+                "reference": "f6bf37a272fda164f6c451407c99f820eb1eb95b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/illuminate/contracts/zipball/6c39fba7b2311e28f5c6ac7d729e3d49a2a98406",
-                "reference": "6c39fba7b2311e28f5c6ac7d729e3d49a2a98406",
+                "url": "https://api.github.com/repos/illuminate/contracts/zipball/f6bf37a272fda164f6c451407c99f820eb1eb95b",
+                "reference": "f6bf37a272fda164f6c451407c99f820eb1eb95b",
                 "shasum": ""
             },
             "require": {
@@ -1433,20 +1433,20 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2023-09-05T19:07:46+00:00"
+            "time": "2023-10-30T00:59:22+00:00"
         },
         {
             "name": "illuminate/database",
-            "version": "v10.29.0",
+            "version": "v10.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/database.git",
-                "reference": "bfe1d2bcd955db6325709c36d098b2d9bd4d7c5d"
+                "reference": "1cd72b9404b41c3ed4c9d443679e8016030ae82b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/illuminate/database/zipball/bfe1d2bcd955db6325709c36d098b2d9bd4d7c5d",
-                "reference": "bfe1d2bcd955db6325709c36d098b2d9bd4d7c5d",
+                "url": "https://api.github.com/repos/illuminate/database/zipball/1cd72b9404b41c3ed4c9d443679e8016030ae82b",
+                "reference": "1cd72b9404b41c3ed4c9d443679e8016030ae82b",
                 "shasum": ""
             },
             "require": {
@@ -1502,11 +1502,11 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2023-10-20T10:10:54+00:00"
+            "time": "2023-10-30T14:29:38+00:00"
         },
         {
             "name": "illuminate/macroable",
-            "version": "v10.29.0",
+            "version": "v10.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/macroable.git",
@@ -1552,7 +1552,7 @@
         },
         {
             "name": "illuminate/pagination",
-            "version": "v10.29.0",
+            "version": "v10.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/pagination.git",
@@ -1602,16 +1602,16 @@
         },
         {
             "name": "illuminate/support",
-            "version": "v10.29.0",
+            "version": "v10.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/support.git",
-                "reference": "e46e5864314d59fa690637e51d6cd2113acb2e7b"
+                "reference": "5244b825e8988db7c91254c31ce224dc3892eeae"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/illuminate/support/zipball/e46e5864314d59fa690637e51d6cd2113acb2e7b",
-                "reference": "e46e5864314d59fa690637e51d6cd2113acb2e7b",
+                "url": "https://api.github.com/repos/illuminate/support/zipball/5244b825e8988db7c91254c31ce224dc3892eeae",
+                "reference": "5244b825e8988db7c91254c31ce224dc3892eeae",
                 "shasum": ""
             },
             "require": {
@@ -1669,7 +1669,7 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2023-10-18T14:12:13+00:00"
+            "time": "2023-10-30T01:00:17+00:00"
         },
         {
             "name": "irazasyed/telegram-bot-sdk",
@@ -1815,16 +1815,16 @@
         },
         {
             "name": "lcobucci/jwt",
-            "version": "5.0.0",
+            "version": "5.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/lcobucci/jwt.git",
-                "reference": "47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34"
+                "reference": "f0031c07b96db6a0ca649206e7eacddb7e9d5908"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/lcobucci/jwt/zipball/47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34",
-                "reference": "47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34",
+                "url": "https://api.github.com/repos/lcobucci/jwt/zipball/f0031c07b96db6a0ca649206e7eacddb7e9d5908",
+                "reference": "f0031c07b96db6a0ca649206e7eacddb7e9d5908",
                 "shasum": ""
             },
             "require": {
@@ -1832,20 +1832,20 @@
                 "ext-json": "*",
                 "ext-openssl": "*",
                 "ext-sodium": "*",
-                "php": "~8.1.0 || ~8.2.0",
+                "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
                 "psr/clock": "^1.0"
             },
             "require-dev": {
-                "infection/infection": "^0.26.19",
+                "infection/infection": "^0.27.0",
                 "lcobucci/clock": "^3.0",
-                "lcobucci/coding-standard": "^9.0",
-                "phpbench/phpbench": "^1.2.8",
+                "lcobucci/coding-standard": "^11.0",
+                "phpbench/phpbench": "^1.2.9",
                 "phpstan/extension-installer": "^1.2",
-                "phpstan/phpstan": "^1.10.3",
-                "phpstan/phpstan-deprecation-rules": "^1.1.2",
-                "phpstan/phpstan-phpunit": "^1.3.8",
+                "phpstan/phpstan": "^1.10.7",
+                "phpstan/phpstan-deprecation-rules": "^1.1.3",
+                "phpstan/phpstan-phpunit": "^1.3.10",
                 "phpstan/phpstan-strict-rules": "^1.5.0",
-                "phpunit/phpunit": "^10.0.12"
+                "phpunit/phpunit": "^10.2.6"
             },
             "suggest": {
                 "lcobucci/clock": ">= 3.0"
@@ -1874,7 +1874,7 @@
             ],
             "support": {
                 "issues": "https://github.com/lcobucci/jwt/issues",
-                "source": "https://github.com/lcobucci/jwt/tree/5.0.0"
+                "source": "https://github.com/lcobucci/jwt/tree/5.1.0"
             },
             "funding": [
                 {
@@ -1886,7 +1886,7 @@
                     "type": "patreon"
                 }
             ],
-            "time": "2023-02-25T21:35:16+00:00"
+            "time": "2023-10-31T06:41:47+00:00"
         },
         {
             "name": "league/event",
@@ -4961,16 +4961,16 @@
         },
         {
             "name": "symfony/http-client",
-            "version": "v6.3.6",
+            "version": "v6.3.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-client.git",
-                "reference": "ab8446f997efb9913627e9da10fa784d2182fe92"
+                "reference": "cd67fcaf4524ec6ae5d9b2d9497682d7ad3ce57d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-client/zipball/ab8446f997efb9913627e9da10fa784d2182fe92",
-                "reference": "ab8446f997efb9913627e9da10fa784d2182fe92",
+                "url": "https://api.github.com/repos/symfony/http-client/zipball/cd67fcaf4524ec6ae5d9b2d9497682d7ad3ce57d",
+                "reference": "cd67fcaf4524ec6ae5d9b2d9497682d7ad3ce57d",
                 "shasum": ""
             },
             "require": {
@@ -5033,7 +5033,7 @@
                 "http"
             ],
             "support": {
-                "source": "https://github.com/symfony/http-client/tree/v6.3.6"
+                "source": "https://github.com/symfony/http-client/tree/v6.3.7"
             },
             "funding": [
                 {
@@ -5049,7 +5049,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-10-06T10:08:56+00:00"
+            "time": "2023-10-29T12:41:36+00:00"
         },
         {
             "name": "symfony/http-client-contracts",
@@ -5131,16 +5131,16 @@
         },
         {
             "name": "symfony/http-foundation",
-            "version": "v6.3.6",
+            "version": "v6.3.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-foundation.git",
-                "reference": "c186627f52febe09c6d5270b04f8462687a250a6"
+                "reference": "59d1837d5d992d16c2628cd0d6b76acf8d69b33e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c186627f52febe09c6d5270b04f8462687a250a6",
-                "reference": "c186627f52febe09c6d5270b04f8462687a250a6",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/59d1837d5d992d16c2628cd0d6b76acf8d69b33e",
+                "reference": "59d1837d5d992d16c2628cd0d6b76acf8d69b33e",
                 "shasum": ""
             },
             "require": {
@@ -5188,7 +5188,7 @@
             "description": "Defines an object-oriented layer for the HTTP specification",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-foundation/tree/v6.3.6"
+                "source": "https://github.com/symfony/http-foundation/tree/v6.3.7"
             },
             "funding": [
                 {
@@ -5204,7 +5204,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-10-17T11:32:53+00:00"
+            "time": "2023-10-28T23:55:27+00:00"
         },
         {
             "name": "symfony/options-resolver",
@@ -5273,88 +5273,6 @@
             ],
             "time": "2023-05-12T14:21:09+00:00"
         },
-        {
-            "name": "symfony/polyfill-ctype",
-            "version": "v1.28.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
-                "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "provide": {
-                "ext-ctype": "*"
-            },
-            "suggest": {
-                "ext-ctype": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.28-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Ctype\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Gert de Pagter",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for ctype functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "ctype",
-                "polyfill",
-                "portable"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-01-26T09:26:14+00:00"
-        },
         {
             "name": "symfony/polyfill-iconv",
             "version": "v1.28.0",
@@ -6009,16 +5927,16 @@
         },
         {
             "name": "symfony/translation",
-            "version": "v6.3.6",
+            "version": "v6.3.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "869b26c7a9d4b8a48afdd77ab36031909c87e3a2"
+                "reference": "30212e7c87dcb79c83f6362b00bde0e0b1213499"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/869b26c7a9d4b8a48afdd77ab36031909c87e3a2",
-                "reference": "869b26c7a9d4b8a48afdd77ab36031909c87e3a2",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/30212e7c87dcb79c83f6362b00bde0e0b1213499",
+                "reference": "30212e7c87dcb79c83f6362b00bde0e0b1213499",
                 "shasum": ""
             },
             "require": {
@@ -6084,7 +6002,7 @@
             "description": "Provides tools to internationalize your application",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/translation/tree/v6.3.6"
+                "source": "https://github.com/symfony/translation/tree/v6.3.7"
             },
             "funding": [
                 {
@@ -6100,7 +6018,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-10-17T11:32:53+00:00"
+            "time": "2023-10-28T23:11:45+00:00"
         },
         {
             "name": "symfony/translation-contracts",
@@ -6180,78 +6098,6 @@
             ],
             "time": "2023-05-30T17:17:10+00:00"
         },
-        {
-            "name": "symfony/yaml",
-            "version": "v6.3.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/yaml.git",
-                "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/e23292e8c07c85b971b44c1c4b87af52133e2add",
-                "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=8.1",
-                "symfony/deprecation-contracts": "^2.5|^3",
-                "symfony/polyfill-ctype": "^1.8"
-            },
-            "conflict": {
-                "symfony/console": "<5.4"
-            },
-            "require-dev": {
-                "symfony/console": "^5.4|^6.0"
-            },
-            "bin": [
-                "Resources/bin/yaml-lint"
-            ],
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Yaml\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Loads and dumps YAML files",
-            "homepage": "https://symfony.com",
-            "support": {
-                "source": "https://github.com/symfony/yaml/tree/v6.3.3"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-07-31T07:08:24+00:00"
-        },
         {
             "name": "tronovav/geoip2-update",
             "version": "v2.3.1",
@@ -7045,16 +6891,16 @@
         },
         {
             "name": "friendsofphp/php-cs-fixer",
-            "version": "v3.36.0",
+            "version": "v3.37.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
-                "reference": "e889301f276c6ce2c391d6aafae9a162e785b064"
+                "reference": "c3fe76976081ab871aa654e872da588077e19679"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/e889301f276c6ce2c391d6aafae9a162e785b064",
-                "reference": "e889301f276c6ce2c391d6aafae9a162e785b064",
+                "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/c3fe76976081ab871aa654e872da588077e19679",
+                "reference": "c3fe76976081ab871aa654e872da588077e19679",
                 "shasum": ""
             },
             "require": {
@@ -7126,7 +6972,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.36.0"
+                "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.37.1"
             },
             "funding": [
                 {
@@ -7134,7 +6980,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-10-27T10:12:29+00:00"
+            "time": "2023-10-29T20:51:23+00:00"
         },
         {
             "name": "justinrainbow/json-schema",
@@ -9777,6 +9623,88 @@
             ],
             "time": "2023-09-26T12:56:25+00:00"
         },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.28.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
+                "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-ctype": "*"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.28-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-01-26T09:26:14+00:00"
+        },
         {
             "name": "symfony/polyfill-php81",
             "version": "v1.28.0",
@@ -10208,6 +10136,7 @@
         "ext-posix": "*",
         "ext-redis": "*",
         "ext-xml": "*",
+        "ext-yaml": "*",
         "ext-zip": "*"
     },
     "platform-dev": [],

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

@@ -80,7 +80,7 @@ $_ENV['auto_detect_ban_time']        = 60;          // 每次封禁的时长 (
 //节点检测-----------------------------------------------------------------------------------------------
 #GFW检测
 $_ENV['detect_gfw_port']     = 443;                                                  //所有节点服务器都打开的TCP端口
-$_ENV['detect_gfw_url']      = 'http://example.com:8080/tcping?ip={ip}&port={port}'; //检测节点是否被gfw墙了的API的URL
+$_ENV['detect_gfw_url']      = 'http://example.com:8080/v1/tcping?ip={ip}&port={port}'; //检测节点是否被gfw墙了的API的URL
 
 #离线检测
 $_ENV['enable_detect_offline']  = true;

+ 3 - 0
resources/views/tabler/admin/coupon.tpl

@@ -1,6 +1,9 @@
 {include file='admin/header.tpl'}
 
 <link rel="stylesheet" href="//{$config['jsdelivr_url']}/npm/flatpickr/dist/flatpickr.min.css">
+{if $user->is_dark_mode}
+    <link rel="stylesheet" href="//{$config['jsdelivr_url']}/npm/flatpickr/dist/themes/dark.min.css">
+{/if}
 <script src="//{$config['jsdelivr_url']}/npm/flatpickr"></script>
 <script src="//{$config['jsdelivr_url']}/npm/flatpickr/dist/l10n/zh.js"></script>
 

+ 2 - 2
resources/views/tabler/admin/header.tpl

@@ -193,7 +193,7 @@
                                     <i class="ti ti-friends"></i>&nbsp;
                                     邀请
                                 </a>
-                                <a class="dropdown-item" href="/admin/moneylog">
+                                <a class="dropdown-item" href="/admin/money">
                                     <i class="ti ti-coin"></i>&nbsp;
                                     余额
                                 </a>
@@ -205,7 +205,7 @@
                                     <i class="ti ti-router"></i>&nbsp;
                                     在线IP
                                 </a>
-                                <a class="dropdown-item" href="/admin/trafficlog">
+                                <a class="dropdown-item" href="/admin/traffic">
                                     <i class="ti ti-arrows-up-down"></i>&nbsp;
                                     流量使用
                                 </a>

+ 1 - 1
resources/views/tabler/admin/log/money.tpl

@@ -40,7 +40,7 @@
     <script>
         var table = $('#data_table').DataTable({
             ajax: {
-                url: '/admin/moneylog/ajax',
+                url: '/admin/money/ajax',
                 type: 'POST',
                 dataSrc: 'money_logs'
             },

+ 1 - 1
resources/views/tabler/admin/log/traffic.tpl

@@ -43,7 +43,7 @@
             "searching": false,
             "ordering": false,
             ajax: {
-                url: '/admin/trafficlog/ajax',
+                url: '/admin/traffic/ajax',
                 type: 'POST',
                 dataSrc: 'trafficlogs.data'
             },

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

@@ -228,7 +228,7 @@
 
     $("#reset-node-password").click(function () {
         $.ajax({
-            url: '/admin/node/{$node->id}/password_reset',
+            url: '/admin/node/{$node->id}/reset',
             type: 'POST',
             dataType: "json",
             success: function (data) {

+ 1 - 1
resources/views/tabler/admin/setting/email.tpl

@@ -367,7 +367,7 @@
 
             $("#test-email").click(function () {
                 $.ajax({
-                    url: '/admin/setting/test_email',
+                    url: '/admin/setting/test/email',
                     type: 'POST',
                     dataType: "json",
                     data: {

+ 3 - 3
resources/views/tabler/admin/setting/im.tpl

@@ -519,7 +519,7 @@
 
             $("#test-telegram").click(function () {
                 $.ajax({
-                    url: '/admin/setting/test_telegram',
+                    url: '/admin/setting/test/telegram',
                     type: 'POST',
                     dataType: "json",
                     data: {
@@ -539,7 +539,7 @@
 
             $("#test-discord").click(function () {
                 $.ajax({
-                    url: '/admin/setting/test_discord',
+                    url: '/admin/setting/test/discord',
                     type: 'POST',
                     dataType: "json",
                     data: {
@@ -559,7 +559,7 @@
 
             $("#test-slack").click(function () {
                 $.ajax({
-                    url: '/admin/setting/test_slack',
+                    url: '/admin/setting/test/slack',
                     type: 'POST',
                     dataType: "json",
                     data: {

+ 3 - 3
resources/views/tabler/admin/system.tpl

@@ -50,12 +50,12 @@
                 type: 'POST',
                 dataType: "json",
                 success: function (data) {
-                    if (data.is_upto_date === true) {
+                    if (data.is_upto_date) {
                         $('.badge').remove();
-                        $('#version').append('<span class="badge bg-green">已是最新版本</span>');
+                        $('#version').append('<span class="badge bg-green text-green-fg">已是最新版本</span>');
                     } else {
                         $('.badge').remove();
-                        $('#version').append('<span class="badge bg-red">有新版本 ' + data.latest_version + ' 可用</span>');
+                        $('#version').append('<span class="badge bg-red text-red-fg">有新版本 ' + data.latest_version + ' 可用</span>');
                     }
                 }
             })

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

@@ -104,7 +104,7 @@
                                             资料
                                         </a>
                                         {if $public_setting['subscribe_log']}
-                                            <a class="dropdown-item" href="/user/subscribe/log">
+                                            <a class="dropdown-item" href="/user/subscribe">
                                                 <i class="ti ti-rss"></i></i>&nbsp;
                                                 订阅
                                             </a>

+ 1 - 1
resources/views/tabler/user/invoice/view.tpl

@@ -145,7 +145,7 @@
                             $('#success-dialog').modal('show');
                             setTimeout(function () {
                                 $(location).attr('href', '/user/invoice');
-                            }, 1500);
+                            }, {$config['jump_delay']});
                         } else {
                             $('#fail-message').text(data.msg);
                             $('#fail-dialog').modal('show');

+ 1 - 1
resources/views/tabler/user/order/create.tpl

@@ -169,7 +169,7 @@
                         $('#success-dialog').modal('show');
                         setTimeout(function () {
                             $(location).attr('href', '/user/invoice/' + data.invoice_id + '/view');
-                        }, $config['jump_delay']);
+                        }, {$config['jump_delay']});
                     } else {
                         $('#fail-message').text(data.msg);
                         $('#fail-dialog').modal('show');

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

@@ -18,6 +18,7 @@ use Slim\Http\ServerRequest;
 use Telegram\Bot\Exceptions\TelegramSDKException;
 use function str_replace;
 use function strip_tags;
+use function time;
 use const PHP_EOL;
 
 final class AnnController extends BaseController

+ 1 - 1
src/Controllers/Admin/CouponController.php

@@ -253,7 +253,7 @@ final class CouponController extends BaseController
             $coupon->product_id = $limit->product_id;
             $coupon->use_time = (int) $limit->use_time < 0 ? '不限次数' : $limit->use_time;
             $coupon->total_use_time = ! property_exists($limit, 'total_use_time') ||
-                (int) $limit->total_use_time < 0 ? '不限次数' : $limit->total_use_time;
+            (int) $limit->total_use_time < 0 ? '不限次数' : $limit->total_use_time;
             $coupon->new_user = $limit->new_user === 1 ? '是' : '否';
             $coupon->disabled = $limit->disabled === 1 ? '是' : '否';
             $coupon->create_time = Tools::toDateTime((int) $coupon->create_time);

+ 67 - 0
src/Controllers/Admin/DetectBanController.php

@@ -0,0 +1,67 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Controllers\Admin;
+
+use App\Controllers\BaseController;
+use App\Models\DetectBanLog;
+use App\Utils\Tools;
+use Exception;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Http\Response;
+use Slim\Http\ServerRequest;
+
+final class DetectBanController extends BaseController
+{
+    private static array $details =
+        [
+            'field' => [
+                'id' => '事件ID',
+                'user_name' => '用户名',
+                'user_id' => '用户ID',
+                'email' => '用户邮箱',
+                'detect_number' => '违规次数',
+                'ban_time' => '封禁时长(分钟)',
+                'start_time' => '统计开始时间',
+                'end_time' => '统计结束&封禁开始时间',
+                'ban_end_time' => '封禁结束时间',
+                'all_detect_number' => '累计违规次数',
+            ],
+        ];
+
+    /**
+     * @throws Exception
+     */
+    public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        return $response->write(
+            $this->view()
+                ->assign('details', self::$details)
+                ->fetch('admin/log/detect_ban.tpl')
+        );
+    }
+
+    public function ajax(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $length = $request->getParam('length');
+        $page = $request->getParam('start') / $length + 1;
+        $draw = $request->getParam('draw');
+
+        $bans = DetectBanLog::orderBy('id', 'desc')->paginate($length, '*', '', $page);
+        $total = DetectBanLog::count();
+
+        foreach ($bans as $ban) {
+            $ban->start_time = Tools::toDateTime((int) $ban->start_time);
+            $ban->end_time = Tools::toDateTime((int) $ban->end_time);
+            $ban->ban_end_time = $ban->banEndTime();
+        }
+
+        return $response->withJson([
+            'draw' => $draw,
+            'recordsTotal' => $total,
+            'recordsFiltered' => $total,
+            'bans' => $bans,
+        ]);
+    }
+}

+ 0 - 236
src/Controllers/Admin/DetectController.php

@@ -1,236 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Controllers\Admin;
-
-use App\Controllers\BaseController;
-use App\Models\DetectBanLog;
-use App\Models\DetectLog;
-use App\Models\DetectRule;
-use App\Services\IM\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;
-
-final class DetectController extends BaseController
-{
-    private static array $rule_details =
-    [
-        'field' => [
-            'op' => '操作',
-            'id' => '规则ID',
-            'name' => '规则名称',
-            'text' => '规则介绍',
-            'regex' => '正则表达式',
-            'type' => '规则类型',
-        ],
-        'add_dialog' => [
-            [
-                'id' => 'name',
-                'info' => '规则名称',
-                'type' => 'input',
-                'placeholder' => '审计规则名称',
-            ],
-            [
-                'id' => 'text',
-                'info' => '规则介绍',
-                'type' => 'input',
-                'placeholder' => '简洁明了地描述审计规则',
-            ],
-            [
-                'id' => 'regex',
-                'info' => '正则表达式',
-                'type' => 'input',
-                'placeholder' => '用以匹配审计内容的正则表达式',
-            ],
-            [
-                'id' => 'type',
-                'info' => '规则类型',
-                'type' => 'select',
-                'select' => [
-                    '1' => '数据包明文匹配',
-                    '0' => '数据包十六进制匹配',
-                ],
-            ],
-        ],
-    ];
-
-    private static array $log_details =
-    [
-        'field' => [
-            'id' => '事件ID',
-            'user_id' => '用户ID',
-            'user_name' => '用户名',
-            'node_id' => '节点ID',
-            'node_name' => '节点名',
-            'list_id' => '规则ID',
-            'rule_name' => '规则名',
-            'rule_text' => '规则描述',
-            'rule_regex' => '规则正则表达式',
-            'rule_type' => '规则类型',
-            'datetime' => '时间',
-        ],
-    ];
-
-    private static array $ban_details =
-    [
-        'field' => [
-            'id' => '事件ID',
-            'user_name' => '用户名',
-            'user_id' => '用户ID',
-            'email' => '用户邮箱',
-            'detect_number' => '违规次数',
-            'ban_time' => '封禁时长(分钟)',
-            'start_time' => '统计开始时间',
-            'end_time' => '统计结束&封禁开始时间',
-            'ban_end_time' => '封禁结束时间',
-            'all_detect_number' => '累计违规次数',
-        ],
-    ];
-
-    /**
-     * @throws Exception
-     */
-    public function detect(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        return $response->write(
-            $this->view()
-                ->assign('details', self::$rule_details)
-                ->fetch('admin/detect.tpl')
-        );
-    }
-
-    /**
-     * @throws TelegramSDKException
-     */
-    public function add(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $rule = new DetectRule();
-        $rule->name = $request->getParam('name');
-        $rule->text = $request->getParam('text');
-        $rule->regex = $request->getParam('regex');
-        $rule->type = $request->getParam('type');
-
-        if (! $rule->save()) {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => '添加失败',
-            ]);
-        }
-
-        (new Telegram())->sendMarkdown(0, '有新的审计规则:' . $rule->name);
-        return $response->withJson([
-            'ret' => 1,
-            'msg' => '添加成功',
-        ]);
-    }
-
-    public function delete(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $id = $args['id'];
-        $rule = DetectRule::find($id);
-        if (! $rule->delete()) {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => '删除失败',
-            ]);
-        }
-        return $response->withJson([
-            'ret' => 1,
-            'msg' => '删除成功',
-        ]);
-    }
-
-    /**
-     * @throws Exception
-     */
-    public function log(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        return $response->write(
-            $this->view()
-                ->assign('details', self::$log_details)
-                ->fetch('admin/log/detect.tpl')
-        );
-    }
-
-    /**
-     * @throws Exception
-     */
-    public function ban(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        return $response->write(
-            $this->view()
-                ->assign('details', self::$ban_details)
-                ->fetch('admin/log/detect_ban.tpl')
-        );
-    }
-
-    public function ajaxRule(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $rules = DetectRule::orderBy('id', 'desc')->get();
-
-        foreach ($rules as $rule) {
-            $rule->op = '<button type="button" class="btn btn-red" id="delete-rule-' . $rule->id .
-            '" onclick="deleteRule(' . $rule->id . ')">删除</button>';
-            $rule->type = $rule->type();
-        }
-
-        return $response->withJson([
-            'rules' => $rules,
-        ]);
-    }
-
-    public function ajaxLog(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $length = $request->getParam('length');
-        $page = $request->getParam('start') / $length + 1;
-        $draw = $request->getParam('draw');
-
-        $logs = DetectLog::orderBy('id', 'desc')->paginate($length, '*', '', $page);
-        $total = DetectLog::count();
-
-        foreach ($logs as $log) {
-            $log->user_name = $log->userName();
-            $log->node_name = $log->nodeName();
-            $log->rule_name = $log->ruleName();
-            $log->rule_text = $log->ruleText();
-            $log->rule_regex = $log->ruleRegex();
-            $log->rule_type = $log->ruleType();
-            $log->datetime = Tools::toDateTime((int) $log->datetime);
-        }
-
-        return $response->withJson([
-            'draw' => $draw,
-            'recordsTotal' => $total,
-            'recordsFiltered' => $total,
-            'logs' => $logs,
-        ]);
-    }
-
-    public function ajaxBan(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $length = $request->getParam('length');
-        $page = $request->getParam('start') / $length + 1;
-        $draw = $request->getParam('draw');
-
-        $bans = DetectBanLog::orderBy('id', 'desc')->paginate($length, '*', '', $page);
-        $total = DetectBanLog::count();
-
-        foreach ($bans as $ban) {
-            $ban->start_time = Tools::toDateTime((int) $ban->start_time);
-            $ban->end_time = Tools::toDateTime((int) $ban->end_time);
-            $ban->ban_end_time = $ban->banEndTime();
-        }
-
-        return $response->withJson([
-            'draw' => $draw,
-            'recordsTotal' => $total,
-            'recordsFiltered' => $total,
-            'bans' => $bans,
-        ]);
-    }
-}

+ 72 - 0
src/Controllers/Admin/DetectLogController.php

@@ -0,0 +1,72 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Controllers\Admin;
+
+use App\Controllers\BaseController;
+use App\Models\DetectLog;
+use App\Utils\Tools;
+use Exception;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Http\Response;
+use Slim\Http\ServerRequest;
+
+final class DetectLogController extends BaseController
+{
+    private static array $details =
+        [
+            'field' => [
+                'id' => '事件ID',
+                'user_id' => '用户ID',
+                'user_name' => '用户名',
+                'node_id' => '节点ID',
+                'node_name' => '节点名',
+                'list_id' => '规则ID',
+                'rule_name' => '规则名',
+                'rule_text' => '规则描述',
+                'rule_regex' => '规则正则表达式',
+                'rule_type' => '规则类型',
+                'datetime' => '时间',
+            ],
+        ];
+
+    /**
+     * @throws Exception
+     */
+    public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        return $response->write(
+            $this->view()
+                ->assign('details', self::$details)
+                ->fetch('admin/log/detect.tpl')
+        );
+    }
+
+    public function ajax(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $length = $request->getParam('length');
+        $page = $request->getParam('start') / $length + 1;
+        $draw = $request->getParam('draw');
+
+        $logs = DetectLog::orderBy('id', 'desc')->paginate($length, '*', '', $page);
+        $total = DetectLog::count();
+
+        foreach ($logs as $log) {
+            $log->user_name = $log->userName();
+            $log->node_name = $log->nodeName();
+            $log->rule_name = $log->ruleName();
+            $log->rule_text = $log->ruleText();
+            $log->rule_regex = $log->ruleRegex();
+            $log->rule_type = $log->ruleType();
+            $log->datetime = Tools::toDateTime((int) $log->datetime);
+        }
+
+        return $response->withJson([
+            'draw' => $draw,
+            'recordsTotal' => $total,
+            'recordsFiltered' => $total,
+            'logs' => $logs,
+        ]);
+    }
+}

+ 126 - 0
src/Controllers/Admin/DetectRuleController.php

@@ -0,0 +1,126 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Controllers\Admin;
+
+use App\Controllers\BaseController;
+use App\Models\DetectRule;
+use App\Services\IM\Telegram;
+use Exception;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Http\Response;
+use Slim\Http\ServerRequest;
+use Telegram\Bot\Exceptions\TelegramSDKException;
+
+final class DetectRuleController extends BaseController
+{
+    private static array $details =
+        [
+            'field' => [
+                'op' => '操作',
+                'id' => '规则ID',
+                'name' => '规则名称',
+                'text' => '规则介绍',
+                'regex' => '正则表达式',
+                'type' => '规则类型',
+            ],
+            'add_dialog' => [
+                [
+                    'id' => 'name',
+                    'info' => '规则名称',
+                    'type' => 'input',
+                    'placeholder' => '审计规则名称',
+                ],
+                [
+                    'id' => 'text',
+                    'info' => '规则介绍',
+                    'type' => 'input',
+                    'placeholder' => '简洁明了地描述审计规则',
+                ],
+                [
+                    'id' => 'regex',
+                    'info' => '正则表达式',
+                    'type' => 'input',
+                    'placeholder' => '用以匹配审计内容的正则表达式',
+                ],
+                [
+                    'id' => 'type',
+                    'info' => '规则类型',
+                    'type' => 'select',
+                    'select' => [
+                        '1' => '数据包明文匹配',
+                        '0' => '数据包十六进制匹配',
+                    ],
+                ],
+            ],
+        ];
+
+    /**
+     * @throws Exception
+     */
+    public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        return $response->write(
+            $this->view()
+                ->assign('details', self::$details)
+                ->fetch('admin/detect.tpl')
+        );
+    }
+
+    /**
+     * @throws TelegramSDKException
+     */
+    public function add(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $rule = new DetectRule();
+        $rule->name = $request->getParam('name');
+        $rule->text = $request->getParam('text');
+        $rule->regex = $request->getParam('regex');
+        $rule->type = $request->getParam('type');
+
+        if (! $rule->save()) {
+            return $response->withJson([
+                'ret' => 0,
+                'msg' => '添加失败',
+            ]);
+        }
+
+        (new Telegram())->sendMarkdown(0, '有新的审计规则:' . $rule->name);
+        return $response->withJson([
+            'ret' => 1,
+            'msg' => '添加成功',
+        ]);
+    }
+
+    public function delete(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $id = $args['id'];
+        $rule = DetectRule::find($id);
+        if (! $rule->delete()) {
+            return $response->withJson([
+                'ret' => 0,
+                'msg' => '删除失败',
+            ]);
+        }
+        return $response->withJson([
+            'ret' => 1,
+            'msg' => '删除成功',
+        ]);
+    }
+
+    public function ajax(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $rules = DetectRule::orderBy('id', 'desc')->get();
+
+        foreach ($rules as $rule) {
+            $rule->op = '<button type="button" class="btn btn-red" id="delete-rule-' . $rule->id .
+                '" onclick="deleteRule(' . $rule->id . ')">删除</button>';
+            $rule->type = $rule->type();
+        }
+
+        return $response->withJson([
+            'rules' => $rules,
+        ]);
+    }
+}

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

@@ -14,6 +14,7 @@ use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 use Telegram\Bot\Exceptions\TelegramSDKException;
+use function time;
 
 final class DocsController extends BaseController
 {

+ 1 - 0
src/Controllers/Admin/GiftCardController.php

@@ -12,6 +12,7 @@ use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 use function time;
+use const PHP_EOL;
 
 final class GiftCardController extends BaseController
 {

+ 1 - 1
src/Controllers/Admin/InviteController.php

@@ -63,7 +63,7 @@ final class InviteController extends BaseController
      *
      * @throws Exception
      */
-    public function invite(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         return $response->write(
             $this->view()

+ 11 - 11
src/Controllers/Admin/MoneyLogController.php

@@ -15,17 +15,17 @@ use Slim\Http\ServerRequest;
 final class MoneyLogController extends BaseController
 {
     private static array $details =
-    [
-        'field' => [
-            'id' => '事件ID',
-            'user_id' => '用户ID',
-            'before' => '变动前余额',
-            'after' => '变动后余额',
-            'amount' => '变动金额',
-            'remark' => '备注',
-            'create_time' => '变动时间',
-        ],
-    ];
+        [
+            'field' => [
+                'id' => '事件ID',
+                'user_id' => '用户ID',
+                'before' => '变动前余额',
+                'after' => '变动后余额',
+                'amount' => '变动金额',
+                'remark' => '备注',
+                'create_time' => '变动时间',
+            ],
+        ];
 
     /**
      * 后台用户余额记录页面

+ 51 - 68
src/Controllers/Admin/NodeController.php

@@ -15,7 +15,11 @@ use Exception;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
+use function explode;
+use function json_decode;
 use function json_encode;
+use function round;
+use function str_replace;
 use function trim;
 
 final class NodeController extends BaseController
@@ -92,68 +96,35 @@ final class NodeController extends BaseController
      */
     public function add(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
-        $name = $request->getParam('name') ?? '';
-        $server = trim($request->getParam('server'));
-        $traffic_rate = $request->getParam('traffic_rate') ?? 1;
-        $is_dynamic_rate = $request->getParam('is_dynamic_rate') === 'true' ? 1 : 0;
-        $max_rate = $request->getParam('max_rate') ?? 1;
-        $max_rate_time = $request->getParam('max_rate_time') ?? 0;
-        $min_rate = $request->getParam('min_rate') ?? 1;
-        $min_rate_time = $request->getParam('min_rate_time') ?? 0;
-        $custom_config = $request->getParam('custom_config') ?? '{}';
-        $info = $request->getParam('info') ?? '';
-        $type = $request->getParam('type') === 'true' ? 1 : 0;
-        $node_group = $request->getParam('node_group') ?? 0;
-        $node_speedlimit = $request->getParam('node_speedlimit') ?? 0;
-        $sort = $request->getParam('sort') ?? 0;
-        $req_node_ip = trim($request->getParam('node_ip'));
-        $node_class = $request->getParam('node_class') ?? 0;
-        $node_bandwidth_limit = $request->getParam('node_bandwidth_limit') ?? 0;
-        $bandwidthlimit_resetday = $request->getParam('bandwidthlimit_resetday') ?? 0;
-
-        if ($name === '' ||
-            $server === '' ||
-            $traffic_rate === '' ||
-            $node_group === '' ||
-            $node_speedlimit === '' ||
-            $sort === '' ||
-            $node_class === '' ||
-            $node_bandwidth_limit === '' ||
-            $bandwidthlimit_resetday === '') {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => '请确保各项不能为空',
-            ]);
-        }
-
         $node = new Node();
-        $node->name = $name;
-        $node->server = $server;
 
-        $node->traffic_rate = $traffic_rate;
-        $node->is_dynamic_rate = $is_dynamic_rate;
+        $node->name = $request->getParam('name');
+        $node->node_group = $request->getParam('node_group');
+        $node->server = trim($request->getParam('server'));
+
+        $node->traffic_rate = $request->getParam('traffic_rate') ?? 1;
+        $node->is_dynamic_rate = $request->getParam('is_dynamic_rate') === 'true' ? 1 : 0;
         $node->dynamic_rate_config = json_encode([
-            'max_rate' => $max_rate,
-            'max_rate_time' => $max_rate_time,
-            'min_rate' => $min_rate,
-            'min_rate_time' => $min_rate_time,
+            'max_rate' => $request->getParam('max_rate') ?? 1,
+            'max_rate_time' => $request->getParam('max_rate_time') ?? 0,
+            'min_rate' => $request->getParam('min_rate') ?? 1,
+            'min_rate_time' => $request->getParam('min_rate_time') ?? 0,
         ]);
 
+        $custom_config = $request->getParam('custom_config') ?? '{}';
+
         if ($custom_config !== '') {
             $node->custom_config = $custom_config;
         } else {
             $node->custom_config = '{}';
         }
 
-        $node->info = $info;
-        $node->type = $type;
-        $node->node_group = $node_group;
-        $node->node_speedlimit = $node_speedlimit;
-        $node->status = '';
-        $node->sort = $sort;
-        $node->node_class = $node_class;
-        $node->node_bandwidth_limit = $node_bandwidth_limit * 1024 * 1024 * 1024;
-        $node->bandwidthlimit_resetday = $bandwidthlimit_resetday;
+        $node->info = $request->getParam('info');
+        $node->node_speedlimit = $request->getParam('node_speedlimit');
+        $node->type = $request->getParam('type') === 'true' ? 1 : 0;
+        $node->sort = $request->getParam('sort');
+
+        $req_node_ip = trim($request->getParam('node_ip'));
 
         if (Tools::isIPv4($req_node_ip) || Tools::isIPv6($req_node_ip)) {
             $node->changeNodeIp($req_node_ip);
@@ -161,12 +132,16 @@ final class NodeController extends BaseController
             $node->changeNodeIp($server);
         }
 
+        $node->node_class = $request->getParam('node_class');
+        $node->node_bandwidth_limit = Tools::autoBytesR($request->getParam('node_bandwidth_limit'));
+        $node->bandwidthlimit_resetday = $request->getParam('bandwidthlimit_resetday');
+
         $node->password = Tools::genRandomChar(32);
 
         if (! $node->save()) {
             return $response->withJson([
                 'ret' => 0,
-                'msg' => '节点添加失败',
+                'msg' => '添加失败',
             ]);
         }
 
@@ -188,7 +163,7 @@ final class NodeController extends BaseController
             } catch (Exception $e) {
                 return $response->withJson([
                     'ret' => 1,
-                    'msg' => '节点添加成功,但Telegram通知失败',
+                    'msg' => '添加成功,但 Telegram 通知失败',
                     'node_id' => $node->id,
                 ]);
             }
@@ -196,7 +171,7 @@ final class NodeController extends BaseController
 
         return $response->withJson([
             'ret' => 1,
-            'msg' => '节点添加成功',
+            'msg' => '添加成功',
             'node_id' => $node->id,
         ]);
     }
@@ -212,10 +187,10 @@ final class NodeController extends BaseController
         $node = Node::find($id);
 
         $dynamic_rate_config = json_decode($node->dynamic_rate_config);
-        $node->max_rate = $dynamic_rate_config?->max_rate;
-        $node->max_rate_time = $dynamic_rate_config?->max_rate_time;
-        $node->min_rate = $dynamic_rate_config?->min_rate;
-        $node->min_rate_time = $dynamic_rate_config?->min_rate_time;
+        $node->max_rate = $dynamic_rate_config?->max_rate ?? 1;
+        $node->max_rate_time = $dynamic_rate_config?->max_rate_time ?? 3;
+        $node->min_rate = $dynamic_rate_config?->min_rate ?? 1;
+        $node->min_rate_time = $dynamic_rate_config?->min_rate_time ?? 22;
 
         $node->node_bandwidth = Tools::flowToGB($node->node_bandwidth);
         $node->node_bandwidth_limit = Tools::flowToGB($node->node_bandwidth_limit);
@@ -230,18 +205,19 @@ final class NodeController extends BaseController
 
     /**
      * 后台更新指定节点内容
+     *
+     * @throws EndpointException
      */
     public function update(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         $id = $args['id'];
         $node = Node::find($id);
 
-        $custom_config = $request->getParam('custom_config') ?? '{}';
-
         $node->name = $request->getParam('name');
         $node->node_group = $request->getParam('node_group');
         $node->server = trim($request->getParam('server'));
-        $node->traffic_rate = $request->getParam('traffic_rate');
+
+        $node->traffic_rate = $request->getParam('traffic_rate') ?? 1;
         $node->is_dynamic_rate = $request->getParam('is_dynamic_rate') === 'true' ? 1 : 0;
         $node->dynamic_rate_config = json_encode([
             'max_rate' => $request->getParam('max_rate') ?? 1,
@@ -249,10 +225,8 @@ final class NodeController extends BaseController
             'min_rate' => $request->getParam('min_rate') ?? 1,
             'min_rate_time' => $request->getParam('min_rate_time') ?? 0,
         ]);
-        $node->info = $request->getParam('info');
-        $node->node_speedlimit = $request->getParam('node_speedlimit');
-        $node->type = $request->getParam('type') === 'true' ? 1 : 0;
-        $node->sort = $request->getParam('sort');
+
+        $custom_config = $request->getParam('custom_config') ?? '{}';
 
         if ($custom_config !== '') {
             $node->custom_config = $custom_config;
@@ -260,6 +234,11 @@ final class NodeController extends BaseController
             $node->custom_config = '{}';
         }
 
+        $node->info = $request->getParam('info');
+        $node->node_speedlimit = $request->getParam('node_speedlimit');
+        $node->type = $request->getParam('type') === 'true' ? 1 : 0;
+        $node->sort = $request->getParam('sort');
+
         $req_node_ip = trim($request->getParam('node_ip'));
 
         if (Tools::isIPv4($req_node_ip) || Tools::isIPv6($req_node_ip)) {
@@ -268,7 +247,6 @@ final class NodeController extends BaseController
             $node->changeNodeIp($node->server);
         }
 
-        $node->status = '';
         $node->node_class = $request->getParam('node_class');
         $node->node_bandwidth_limit = $request->getParam('node_bandwidth_limit') * 1024 * 1024 * 1024;
         $node->bandwidthlimit_resetday = $request->getParam('bandwidthlimit_resetday');
@@ -280,6 +258,11 @@ final class NodeController extends BaseController
             ]);
         }
 
+        if ($_ENV['cloudflare_enable']) {
+            $domain_name = explode('.' . $_ENV['cloudflare_name'], $node->server);
+            Cloudflare::updateRecord($domain_name[0], $node->node_ip);
+        }
+
         if (Setting::obtain('telegram_update_node')) {
             try {
                 (new Telegram())->send(
@@ -293,7 +276,7 @@ final class NodeController extends BaseController
             } catch (Exception $e) {
                 return $response->withJson([
                     'ret' => 1,
-                    'msg' => '修改成功,但Telegram通知失败',
+                    'msg' => '修改成功,但 Telegram 通知失败',
                 ]);
             }
         }
@@ -304,7 +287,7 @@ final class NodeController extends BaseController
         ]);
     }
 
-    public function resetNodePassword(
+    public function reset(
         ServerRequest $request,
         Response $response,
         array $args

+ 12 - 12
src/Controllers/Admin/PaylistController.php

@@ -15,18 +15,18 @@ use Slim\Http\ServerRequest;
 final class PaylistController extends BaseController
 {
     private static array $details =
-    [
-        'field' => [
-            'id' => '事件ID',
-            'userid' => '用户ID',
-            'total' => '金额',
-            'status' => '状态',
-            'gateway' => '支付网关',
-            'tradeno' => '网关单号',
-            'datetime' => '支付时间',
-            'invoice_id' => '关联账单ID',
-        ],
-    ];
+        [
+            'field' => [
+                'id' => '事件ID',
+                'userid' => '用户ID',
+                'total' => '金额',
+                'status' => '状态',
+                'gateway' => '支付网关',
+                'tradeno' => '网关单号',
+                'datetime' => '支付时间',
+                'invoice_id' => '关联账单ID',
+            ],
+        ];
 
     /**
      * 后台网关记录页面

+ 2 - 2
src/Controllers/Admin/Setting/BillingController.php

@@ -50,7 +50,7 @@ final class BillingController extends BaseController
     /**
      * @throws Exception
      */
-    public function billing($request, $response, $args)
+    public function index($request, $response, $args)
     {
         $settings = Setting::getClass('billing');
 
@@ -64,7 +64,7 @@ final class BillingController extends BaseController
         );
     }
 
-    public function saveBilling($request, $response, $args)
+    public function save($request, $response, $args)
     {
         $gateway_in_use = [];
 

+ 2 - 2
src/Controllers/Admin/Setting/CaptchaController.php

@@ -27,7 +27,7 @@ final class CaptchaController extends BaseController
     /**
      * @throws Exception
      */
-    public function captcha($request, $response, $args)
+    public function index($request, $response, $args)
     {
         $settings = Setting::getClass('captcha');
 
@@ -39,7 +39,7 @@ final class CaptchaController extends BaseController
         );
     }
 
-    public function saveCaptcha($request, $response, $args)
+    public function save($request, $response, $args)
     {
         foreach (self::$update_field as $item) {
             if (! Setting::set($item, $request->getParam($item))) {

+ 2 - 2
src/Controllers/Admin/Setting/CronController.php

@@ -27,7 +27,7 @@ final class CronController extends BaseController
     /**
      * @throws Exception
      */
-    public function cron($request, $response, $args)
+    public function index($request, $response, $args)
     {
         $settings = Setting::getClass('cron');
 
@@ -39,7 +39,7 @@ final class CronController extends BaseController
         );
     }
 
-    public function saveCron($request, $response, $args)
+    public function save($request, $response, $args)
     {
         $daily_job_hour = (int) $request->getParam('daily_job_hour');
         $daily_job_minute = (int) $request->getParam('daily_job_minute');

+ 2 - 2
src/Controllers/Admin/Setting/EmailController.php

@@ -51,7 +51,7 @@ final class EmailController extends BaseController
     /**
      * @throws Exception
      */
-    public function email($request, $response, $args)
+    public function index($request, $response, $args)
     {
         $settings = Setting::getClass('email');
 
@@ -63,7 +63,7 @@ final class EmailController extends BaseController
         );
     }
 
-    public function saveEmail($request, $response, $args)
+    public function save($request, $response, $args)
     {
         foreach (self::$update_field as $item) {
             if (! Setting::set($item, $request->getParam($item))) {

+ 2 - 2
src/Controllers/Admin/Setting/FeatureController.php

@@ -26,7 +26,7 @@ final class FeatureController extends BaseController
     /**
      * @throws Exception
      */
-    public function feature($request, $response, $args)
+    public function index($request, $response, $args)
     {
         $settings = Setting::getClass('feature');
 
@@ -38,7 +38,7 @@ final class FeatureController extends BaseController
         );
     }
 
-    public function saveFeature($request, $response, $args)
+    public function save($request, $response, $args)
     {
         foreach (self::$update_field as $item) {
             if (! Setting::set($item, $request->getParam($item))) {

+ 2 - 2
src/Controllers/Admin/Setting/ImController.php

@@ -64,7 +64,7 @@ final class ImController extends BaseController
     /**
      * @throws Exception
      */
-    public function im($request, $response, $args)
+    public function index($request, $response, $args)
     {
         $settings = Setting::getClass('im');
 
@@ -76,7 +76,7 @@ final class ImController extends BaseController
         );
     }
 
-    public function saveIm($request, $response, $args)
+    public function save($request, $response, $args)
     {
         foreach (self::$update_field as $item) {
             if (! Setting::set($item, $request->getParam($item))) {

+ 2 - 2
src/Controllers/Admin/Setting/RefController.php

@@ -24,7 +24,7 @@ final class RefController extends BaseController
     /**
      * @throws Exception
      */
-    public function ref($request, $response, $args)
+    public function index($request, $response, $args)
     {
         $settings = Setting::getClass('ref');
 
@@ -36,7 +36,7 @@ final class RefController extends BaseController
         );
     }
 
-    public function saveRef($request, $response, $args)
+    public function save($request, $response, $args)
     {
         foreach (self::$update_field as $item) {
             if (! Setting::set($item, $request->getParam($item))) {

+ 2 - 2
src/Controllers/Admin/Setting/RegController.php

@@ -33,7 +33,7 @@ final class RegController extends BaseController
     /**
      * @throws Exception
      */
-    public function reg($request, $response, $args)
+    public function index($request, $response, $args)
     {
         $settings = Setting::getClass('reg');
 
@@ -45,7 +45,7 @@ final class RegController extends BaseController
         );
     }
 
-    public function saveReg($request, $response, $args)
+    public function save($request, $response, $args)
     {
         foreach (self::$update_field as $item) {
             if (! Setting::set($item, $request->getParam($item))) {

+ 3 - 3
src/Controllers/Admin/Setting/SubscribeController.php → src/Controllers/Admin/Setting/SubController.php

@@ -8,7 +8,7 @@ use App\Controllers\BaseController;
 use App\Models\Setting;
 use Exception;
 
-final class SubscribeController extends BaseController
+final class SubController extends BaseController
 {
     private static array $update_field = [
         'enable_forced_replacement',
@@ -21,7 +21,7 @@ final class SubscribeController extends BaseController
     /**
      * @throws Exception
      */
-    public function sub($request, $response, $args)
+    public function index($request, $response, $args)
     {
         $settings = Setting::getClass('subscribe');
 
@@ -33,7 +33,7 @@ final class SubscribeController extends BaseController
         );
     }
 
-    public function saveSub($request, $response, $args)
+    public function save($request, $response, $args)
     {
         foreach (self::$update_field as $item) {
             if (! Setting::set($item, $request->getParam($item))) {

+ 2 - 2
src/Controllers/Admin/Setting/SupportController.php

@@ -25,7 +25,7 @@ final class SupportController extends BaseController
     /**
      * @throws Exception
      */
-    public function support($request, $response, $args)
+    public function index($request, $response, $args)
     {
         $settings = Setting::getClass('support');
 
@@ -37,7 +37,7 @@ final class SupportController extends BaseController
         );
     }
 
-    public function saveSupport($request, $response, $args)
+    public function save($request, $response, $args)
     {
         foreach (self::$update_field as $item) {
             if (! Setting::set($item, $request->getParam($item))) {

+ 12 - 12
src/Controllers/Admin/SubscribeLogController.php → src/Controllers/Admin/SubLogController.php

@@ -13,20 +13,20 @@ use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 
-final class SubscribeLogController extends BaseController
+final class SubLogController extends BaseController
 {
     private static array $details =
-    [
-        'field' => [
-            'id' => '事件ID',
-            'user_id' => '用户ID',
-            'type' => '获取的订阅类型',
-            'request_ip' => '请求IP',
-            'location' => 'IP归属地',
-            'request_time' => '请求时间',
-            'request_user_agent' => '客户端标识符',
-        ],
-    ];
+        [
+            'field' => [
+                'id' => '事件ID',
+                'user_id' => '用户ID',
+                'type' => '获取的订阅类型',
+                'request_ip' => '请求IP',
+                'location' => 'IP归属地',
+                'request_time' => '请求时间',
+                'request_user_agent' => '客户端标识符',
+            ],
+        ];
 
     /**
      * 后台订阅记录页面

+ 3 - 0
src/Controllers/Admin/SystemController.php

@@ -11,6 +11,9 @@ use Exception;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
+use function file_get_contents;
+use function stream_context_create;
+use function version_compare;
 use const VERSION;
 
 final class SystemController extends BaseController

+ 12 - 12
src/Controllers/Admin/TicketController.php

@@ -26,17 +26,17 @@ use function time;
 final class TicketController extends BaseController
 {
     private static array $details =
-    [
-        'field' => [
-            'op' => '操作',
-            'id' => '工单ID',
-            'title' => '主题',
-            'status' => '工单状态',
-            'type' => '工单类型',
-            'userid' => '提交用户',
-            'datetime' => '创建时间',
-        ],
-    ];
+        [
+            'field' => [
+                'op' => '操作',
+                'id' => '工单ID',
+                'title' => '主题',
+                'status' => '工单状态',
+                'type' => '工单类型',
+                'userid' => '提交用户',
+                'datetime' => '创建时间',
+            ],
+        ];
 
     private static string $err_msg = '请求失败';
 
@@ -161,7 +161,7 @@ final class TicketController extends BaseController
      *
      * @throws Exception
      */
-    public function ticketView(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    public function detail(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         $id = $args['id'];
         $ticket = Ticket::where('id', '=', $id)->first();

+ 10 - 9
src/Controllers/Admin/TrafficLogController.php

@@ -11,19 +11,20 @@ use Exception;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
+use function round;
 
 final class TrafficLogController extends BaseController
 {
     private static array $details =
-    [
-        'field' => [
-            'id' => '记录ID',
-            'user_id' => '用户ID',
-            'traffic' => '累计流量/GB',
-            'hourly_usage' => '过去一小时使用流量/GB',
-            'datetime' => '时间',
-        ],
-    ];
+        [
+            'field' => [
+                'id' => '记录ID',
+                'user_id' => '用户ID',
+                'traffic' => '累计流量/GB',
+                'hourly_usage' => '过去一小时使用流量/GB',
+                'datetime' => '时间',
+            ],
+        ];
 
     /**
      * 后台流量记录页面

+ 1 - 1
src/Controllers/Admin/UserController.php

@@ -118,7 +118,7 @@ final class UserController extends BaseController
      * @throws ClientExceptionInterface
      * @throws TelegramSDKException
      */
-    public function createNewUser(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    public function create(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         $email = $request->getParam('email');
         $ref_by = $request->getParam('ref_by');

+ 37 - 0
src/Controllers/User/DetectLogController.php

@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Controllers\User;
+
+use App\Controllers\BaseController;
+use App\Models\DetectLog;
+use App\Utils\Tools;
+use Exception;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Http\Response;
+use Slim\Http\ServerRequest;
+
+final class DetectLogController extends BaseController
+{
+    /**
+     * 审计碰撞记录
+     *
+     * @throws Exception
+     */
+    public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $logs = DetectLog::orderBy('id', 'desc')->where('user_id', $this->user->id)->get();
+
+        foreach ($logs as $log) {
+            $log->node_name = $log->nodeName();
+            $log->rule = $log->rule();
+            $log->rule->type = $log->ruleType();
+            $log->datetime = Tools::toDateTime($log->datetime);
+        }
+
+        return $response->write($this->view()
+            ->assign('logs', $logs)
+            ->fetch('user/detect/log.tpl'));
+    }
+}

+ 1 - 1
src/Controllers/User/DetectController.php → src/Controllers/User/DetectRuleController.php

@@ -11,7 +11,7 @@ use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 
-final class DetectController extends BaseController
+final class DetectRuleController extends BaseController
 {
     /**
      * @throws Exception

+ 1 - 1
src/Controllers/User/InfoController.php

@@ -37,7 +37,7 @@ final class InfoController extends BaseController
     public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         $themes = Tools::getDir(BASE_PATH . '/resources/views');
-        $methods = Config::getSupportParam('method');
+        $methods = Config::getSsMethod('method');
         $ga_url = MFA::getGaUrl($this->user);
 
         return $response->write($this->view()

+ 1 - 1
src/Controllers/User/InvoiceController.php

@@ -36,7 +36,7 @@ final class InvoiceController extends BaseController
     /**
      * @throws Exception
      */
-    public function invoice(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         return $response->write(
             $this->view()

+ 0 - 62
src/Controllers/User/LogController.php

@@ -1,62 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Controllers\User;
-
-use App\Controllers\BaseController;
-use App\Models\DetectLog;
-use App\Models\SubscribeLog;
-use App\Utils\Tools;
-use Exception;
-use Psr\Http\Message\ResponseInterface;
-use Slim\Http\Response;
-use Slim\Http\ServerRequest;
-
-final class LogController extends BaseController
-{
-    /**
-     * 订阅记录
-     *
-     * @throws Exception
-     */
-    public function subscribe(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $logs = SubscribeLog::orderBy('id', 'desc')->where('user_id', $this->user->id)->get();
-
-        foreach ($logs as $log) {
-            $log->request_time = Tools::toDateTime($log->request_time);
-        }
-
-        return $response->write($this->view()
-            ->assign('logs', $logs)
-            ->fetch('user/subscribe_log.tpl'));
-    }
-
-    /**
-     * 审计碰撞记录
-     *
-     * @throws Exception
-     */
-    public function detect(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
-    {
-        $logs = DetectLog::orderBy('id', 'desc')->where('user_id', $this->user->id)->get();
-
-        foreach ($logs as $log) {
-            $log->node_name = $log->nodeName();
-            $log->rule = $log->rule();
-
-            if ($log->rule->type === 1) {
-                $log->rule->type = '数据包明文匹配';
-            } elseif ($log->type === 2) {
-                $log->rule->type = '数据包 hex 匹配';
-            }
-
-            $log->datetime = Tools::toDateTime($log->datetime);
-        }
-
-        return $response->write($this->view()
-            ->assign('logs', $logs)
-            ->fetch('user/detect/log.tpl'));
-    }
-}

+ 1 - 1
src/Controllers/User/MoneyController.php

@@ -23,7 +23,7 @@ final class MoneyController extends BaseController
     /**
      * @throws Exception
      */
-    public function money(ServerRequest $request, Response $response, array $args): ResponseInterface
+    public function index(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         $user = $this->user;
         $moneylogs = UserMoneyLog::where('user_id', $user->id)->orderBy('id', 'desc')->get();

+ 1 - 1
src/Controllers/User/OrderController.php

@@ -45,7 +45,7 @@ final class OrderController extends BaseController
     /**
      * @throws Exception
      */
-    public function order(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         return $response->write(
             $this->view()

+ 1 - 1
src/Controllers/User/ProductController.php

@@ -17,7 +17,7 @@ final class ProductController extends BaseController
     /**
      * @throws Exception
      */
-    public function product(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         $tabps = Product::where('status', '1')
             ->where('type', 'tabp')

+ 1 - 1
src/Controllers/User/ServerController.php

@@ -20,7 +20,7 @@ final class ServerController extends BaseController
     /**
      * @throws Exception
      */
-    public function server(ServerRequest $request, Response $response, array $args): ResponseInterface
+    public function index(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         $user = $this->user;
         $query = Node::query();

+ 34 - 0
src/Controllers/User/SubLogController.php

@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Controllers\User;
+
+use App\Controllers\BaseController;
+use App\Models\SubscribeLog;
+use App\Utils\Tools;
+use Exception;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Http\Response;
+use Slim\Http\ServerRequest;
+
+final class SubLogController extends BaseController
+{
+    /**
+     * 订阅记录
+     *
+     * @throws Exception
+     */
+    public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $logs = SubscribeLog::orderBy('id', 'desc')->where('user_id', $this->user->id)->get();
+
+        foreach ($logs as $log) {
+            $log->request_time = Tools::toDateTime($log->request_time);
+        }
+
+        return $response->write($this->view()
+            ->assign('logs', $logs)
+            ->fetch('user/subscribe_log.tpl'));
+    }
+}

+ 4 - 4
src/Controllers/User/TicketController.php

@@ -35,7 +35,7 @@ final class TicketController extends BaseController
     /**
      * @throws Exception
      */
-    public function ticket(ServerRequest $request, Response $response, array $args): ?ResponseInterface
+    public function index(ServerRequest $request, Response $response, array $args): ?ResponseInterface
     {
         if (! Setting::obtain('enable_ticket')) {
             return $response->withRedirect('/user');
@@ -62,7 +62,7 @@ final class TicketController extends BaseController
      * @throws TelegramSDKException
      * @throws GuzzleException
      */
-    public function ticketAdd(ServerRequest $request, Response $response, array $args): ResponseInterface
+    public function add(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         $title = $request->getParam('title') ?? '';
         $comment = $request->getParam('comment') ?? '';
@@ -119,7 +119,7 @@ final class TicketController extends BaseController
      * @throws TelegramSDKException
      * @throws ClientExceptionInterface
      */
-    public function ticketUpdate(ServerRequest $request, Response $response, array $args): ResponseInterface
+    public function update(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         $id = $args['id'];
         $comment = $request->getParam('comment') ?? '';
@@ -177,7 +177,7 @@ final class TicketController extends BaseController
     /**
      * @throws Exception
      */
-    public function ticketView(ServerRequest $request, Response $response, array $args): ResponseInterface
+    public function detail(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         if (! Setting::obtain('enable_ticket')) {
             return $response->withRedirect('/user');

+ 1 - 1
src/Middleware/AdminApiToken.php → src/Middleware/AdminApi.php

@@ -8,7 +8,7 @@ use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 
-final class AdminApiToken
+final class AdminApi
 {
     /**
      * MID /admin/api/

+ 2 - 2
src/Middleware/Auth.php

@@ -30,9 +30,9 @@ final class Auth implements MiddlewareInterface
             return AppFactory::determineResponseFactory()->createResponse(302)->withHeader('Location', '/auth/login');
         }
 
-        $enablePages = ['/user/banned', '/user/logout'];
+        $bannedUserEnabledPages = ['/user/banned', '/user/logout'];
 
-        if ($user->is_banned && ! in_array($path, $enablePages)) {
+        if ($user->is_banned && ! in_array($path, $bannedUserEnabledPages)) {
             return AppFactory::determineResponseFactory()->createResponse(302)->withHeader('Location', '/user/banned');
         }
 

+ 1 - 1
src/Middleware/UserApiToken.php → src/Middleware/NodeApi.php

@@ -8,7 +8,7 @@ use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 
-final class UserApiToken
+final class NodeApi
 {
     /**
      * MID /user/api

+ 25 - 0
src/Middleware/UserApi.php

@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Middleware;
+
+use Psr\Http\Message\ResponseInterface;
+use Slim\Http\Response;
+use Slim\Http\ServerRequest;
+
+final class UserApi
+{
+    /**
+     * MID /user/api
+     *
+     * @param ServerRequest $request
+     * @param Response $response
+     * @param callable $next
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke($request, $response, $next)
+    {
+    }
+}

+ 1 - 1
src/Models/DetectRule.php

@@ -17,6 +17,6 @@ final class DetectRule extends Model
      */
     public function type(): string
     {
-        return $this->type === 1 ? '数据包明文匹配' : '数据包十六进制匹配';
+        return $this->type === 1 ? '数据包明文匹配' : '数据包 hex 匹配';
     }
 }

+ 0 - 3
src/Services/Auth.php

@@ -15,9 +15,6 @@ final class Auth
         self::getDriver()->login($uid, $time);
     }
 
-    /**
-     * Get current user(cached)
-     */
     public static function getUser(): User
     {
         global $user;

+ 2 - 2
src/Services/Bot/Telegram/Callback.php

@@ -592,7 +592,7 @@ final class Callback
                 // 加密方式更改
                 $keyboard = $back;
                 if (isset($CallbackDataExplode[1])) {
-                    if (in_array($CallbackDataExplode[1], Config::getSupportParam('method'))) {
+                    if (in_array($CallbackDataExplode[1], Config::getSsMethod('method'))) {
                         $temp = $this->user->setMethod($CallbackDataExplode[1]);
                         if ($temp['ok']) {
                             $text = '你当前的加密方式为:' . $this->user->method . PHP_EOL . PHP_EOL . $temp['msg'];
@@ -605,7 +605,7 @@ final class Callback
                 } else {
                     $Encrypts = [];
 
-                    foreach (Config::getSupportParam('method') as $value) {
+                    foreach (Config::getSsMethod('method') as $value) {
                         $Encrypts[] = [
                             'text' => $value,
                             'callback_data' => 'user.edit.encrypt|' . $value,

+ 1 - 7
src/Services/Config.php

@@ -4,7 +4,6 @@ declare(strict_types=1);
 
 namespace App\Services;
 
-// Config is singleton instance store all config
 final class Config
 {
     public static function getViewConfig(): array
@@ -55,7 +54,7 @@ final class Config
         ];
     }
 
-    public static function getSupportParam($type): array
+    public static function getSsMethod($type): array
     {
         return match ($type) {
             'ss_obfs' => [
@@ -64,11 +63,6 @@ final class Config
                 'simple_obfs_tls',
                 'simple_obfs_tls_compatible',
             ],
-            'ss_2022' => [
-                '2022-blake3-aes-128-gcm',
-                '2022-blake3-aes-256-gcm',
-                '2022-blake3-chacha20-poly1305',
-            ],
             default => [
                 'aes-128-gcm',
                 'aes-192-gcm',

+ 1 - 1
src/Services/Exchange.php

@@ -38,6 +38,6 @@ final class Exchange
             $redis->setex('exchange_rate:' . $from . '_' . $to, 3600, $rate);
         }
 
-        return $rate;
+        return (float) $rate;
     }
 }

+ 4 - 4
src/Services/GeoIP2.php

@@ -29,8 +29,8 @@ final class GeoIP2
      */
     public function getCity(string $ip): ?string
     {
-        $record = $this->city_reader->city($ip);
-        return $record->city->names[$_ENV['geoip_locale']] ?? $record->city->name;
+        $record = $this?->city_reader?->city($ip);
+        return $record?->city?->names[$_ENV['geoip_locale']] ?? $record?->city?->name;
     }
 
     /**
@@ -39,7 +39,7 @@ final class GeoIP2
      */
     public function getCountry(string $ip): ?string
     {
-        $record = $this->country_reader->country($ip);
-        return $record->country->names[$_ENV['geoip_locale']] ?? $record->country->name;
+        $record = $this?->country_reader?->country($ip);
+        return $record?->country?->names[$_ENV['geoip_locale']] ?? $record?->country?->name;
     }
 }

+ 4 - 5
src/Services/Subscribe/Clash.php

@@ -5,9 +5,10 @@ declare(strict_types=1);
 namespace App\Services\Subscribe;
 
 use App\Services\Subscribe;
-use Symfony\Component\Yaml\Yaml;
 use function array_merge;
 use function json_decode;
+use function yaml_emit;
+use const YAML_UTF8_ENCODING;
 
 final class Clash extends Base
 {
@@ -188,11 +189,9 @@ final class Clash extends Base
             'proxies' => $nodes,
         ];
 
-        return Yaml::dump(
+        return yaml_emit(
             array_merge($clash_config, $clash_nodes, $clash_group_config),
-            4,
-            1,
-            Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE
+            YAML_UTF8_ENCODING
         );
     }
 }

+ 1 - 1
src/Utils/Tools.php

@@ -294,7 +294,7 @@ final class Tools
      */
     public static function isParamValidate($type, $str): bool
     {
-        $list = Config::getSupportParam($type);
+        $list = Config::getSsMethod($type);
 
         if (in_array($str, $list)) {
             return true;

+ 3 - 3
tests/App/Services/ConfigTest.php

@@ -132,11 +132,11 @@ class ConfigTest extends TestCase
     }
 
     /**
-     * @covers App\Services\Config::getSupportParam
+     * @covers App\Services\Config::getSsMethod
      */
-    public function testGetSupportParam(): void
+    public function testGetSsMethod(): void
     {
-        $params = Config::getSupportParam('ss_aead_method');
+        $params = Config::getSsMethod('ss_aead_method');
 
         $this->assertIsArray($params);
         $this->assertContains('aes-128-gcm', $params);