ソースを参照

Improve System Blade

- Removed Task config, add those settings to System blade.
- Improve the font-end of system setting blade.
- Improve Payments files to be more  "Modularize"
BrettonYe 4 ヶ月 前
コミット
d9ff6ad6ee
92 ファイル変更1291 行追加1043 行削除
  1. 11 11
      app/Console/Commands/AutoClearLogs.php
  2. 1 1
      app/Console/Commands/ServiceTimer.php
  3. 12 12
      app/Console/Commands/TaskAuto.php
  4. 7 8
      app/Console/Commands/TaskDaily.php
  5. 4 4
      app/Console/Commands/TaskHourly.php
  6. 5 5
      app/Console/Commands/TaskMonthly.php
  7. 1 1
      app/Console/Commands/UserExpireWarning.php
  8. 1 1
      app/Console/Commands/UserTrafficWarning.php
  9. 2 1
      app/Http/Controllers/Admin/LogsController.php
  10. 0 6
      app/Http/Controllers/Admin/MarketingController.php
  11. 1 1
      app/Http/Controllers/Admin/NodeController.php
  12. 1 1
      app/Http/Controllers/Admin/SubscribeController.php
  13. 62 37
      app/Http/Controllers/Admin/SystemController.php
  14. 5 5
      app/Http/Controllers/Api/Client/ClientController.php
  15. 1 1
      app/Http/Controllers/AuthController.php
  16. 3 3
      app/Http/Controllers/UserController.php
  17. 1 1
      app/Models/NodeHeartbeat.php
  18. 6 9
      app/Models/Order.php
  19. 1 1
      app/Models/User.php
  20. 1 1
      app/Models/UserHourlyDataFlow.php
  21. 1 1
      app/Models/VerifyCode.php
  22. 1 1
      app/Notifications/Verification.php
  23. 2 0
      app/Providers/SettingServiceProvider.php
  24. 1 1
      app/Services/OrderService.php
  25. 1 1
      app/Services/UserService.php
  26. 1 1
      app/Utils/Library/AlipayF2F.php
  27. 2 0
      app/Utils/Library/Templates/Gateway.php
  28. 17 7
      app/Utils/Payments/CodePay.php
  29. 6 3
      app/Utils/Payments/Credit.php
  30. 12 5
      app/Utils/Payments/Cryptomus.php
  31. 19 4
      app/Utils/Payments/EPay.php
  32. 13 5
      app/Utils/Payments/F2Fpay.php
  33. 6 3
      app/Utils/Payments/Manual.php
  34. 12 5
      app/Utils/Payments/PayBeaver.php
  35. 13 6
      app/Utils/Payments/PayJs.php
  36. 13 5
      app/Utils/Payments/PayPal.php
  37. 101 0
      app/Utils/Payments/PaymentManager.php
  38. 14 7
      app/Utils/Payments/Stripe.php
  39. 14 4
      app/Utils/Payments/THeadPay.php
  40. 1 1
      app/helpers.php
  41. 0 15
      config/common.php
  42. 0 31
      config/tasks.php
  43. 82 0
      database/migrations/2025_04_26_232606_modified_system.php
  44. 26 26
      database/seeders/ConfigSeeder.php
  45. 1 1
      public/assets/global/fonts/font-awesome/css/all.min.css
  46. 1 1
      public/assets/global/fonts/font-awesome/css/brands.min.css
  47. 1 1
      public/assets/global/fonts/font-awesome/css/fontawesome.min.css
  48. 1 1
      public/assets/global/fonts/font-awesome/css/regular.min.css
  49. 1 1
      public/assets/global/fonts/font-awesome/css/solid.min.css
  50. 1 1
      public/assets/global/fonts/font-awesome/css/svg-with-js.min.css
  51. 1 1
      public/assets/global/fonts/font-awesome/css/v4-font-face.min.css
  52. 1 1
      public/assets/global/fonts/font-awesome/css/v4-shims.min.css
  53. 1 1
      public/assets/global/fonts/font-awesome/css/v5-font-face.min.css
  54. 1 1
      public/assets/global/fonts/font-awesome/js/all.min.js
  55. 1 1
      public/assets/global/fonts/font-awesome/js/brands.min.js
  56. 1 1
      public/assets/global/fonts/font-awesome/js/conflict-detection.min.js
  57. 1 1
      public/assets/global/fonts/font-awesome/js/fontawesome.min.js
  58. 1 1
      public/assets/global/fonts/font-awesome/js/regular.min.js
  59. 1 1
      public/assets/global/fonts/font-awesome/js/solid.min.js
  60. 1 1
      public/assets/global/fonts/font-awesome/js/v4-shims.min.js
  61. BIN
      public/assets/global/fonts/font-awesome/webfonts/fa-brands-400.ttf
  62. BIN
      public/assets/global/fonts/font-awesome/webfonts/fa-brands-400.woff2
  63. BIN
      public/assets/global/fonts/font-awesome/webfonts/fa-regular-400.ttf
  64. BIN
      public/assets/global/fonts/font-awesome/webfonts/fa-regular-400.woff2
  65. BIN
      public/assets/global/fonts/font-awesome/webfonts/fa-solid-900.ttf
  66. BIN
      public/assets/global/fonts/font-awesome/webfonts/fa-solid-900.woff2
  67. BIN
      public/assets/global/fonts/font-awesome/webfonts/fa-v4compatibility.ttf
  68. BIN
      public/assets/global/fonts/font-awesome/webfonts/fa-v4compatibility.woff2
  69. 90 0
      public/assets/global/js/Plugin/toastr.js
  70. 0 0
      public/assets/global/vendor/toastr/toastr.min.css
  71. 0 0
      public/assets/global/vendor/toastr/toastr.min.js
  72. 59 35
      resources/lang/zh_CN/admin.php
  73. 322 482
      resources/views/admin/config/system.blade.php
  74. 11 11
      resources/views/admin/logs/order.blade.php
  75. 107 106
      resources/views/admin/node/info.blade.php
  76. 8 7
      resources/views/auth/layouts.blade.php
  77. 1 1
      resources/views/components/system/input-file.blade.php
  78. 3 3
      resources/views/components/system/input-limit.blade.php
  79. 0 20
      resources/views/components/system/input-test.blade.php
  80. 22 0
      resources/views/components/system/input-unit.blade.php
  81. 20 9
      resources/views/components/system/input.blade.php
  82. 3 3
      resources/views/components/system/select.blade.php
  83. 7 6
      resources/views/components/system/switch.blade.php
  84. 3 2
      resources/views/components/system/tab-pane.blade.php
  85. 12 0
      resources/views/components/system/task-group.blade.php
  86. 3 3
      resources/views/components/system/textarea.blade.php
  87. 10 10
      resources/views/user/components/payment/default.blade.php
  88. 1 6
      resources/views/user/components/purchase.blade.php
  89. 59 59
      resources/views/user/index.blade.php
  90. 17 16
      resources/views/user/layouts.blade.php
  91. 1 1
      routes/web.php
  92. 32 5
      scripts/download_utils.sh

+ 11 - 11
app/Console/Commands/AutoClearLogs.php

@@ -39,27 +39,27 @@ class AutoClearLogs extends Command
     private function clearLog(): void
     {
         try {
-            NodeDailyDataFlow::whereNotNull('node_id')->where('created_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.node_daily_logs'))))->delete(); // 清除节点每天流量数据日志
+            NodeDailyDataFlow::whereNotNull('node_id')->where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.node_daily_logs'))))->delete(); // 清除节点每天流量数据日志
 
-            NodeHourlyDataFlow::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.node_hourly_logs'))))->delete(); // 清除节点每小时流量数据日志
+            NodeHourlyDataFlow::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.node_hourly_logs'))))->delete(); // 清除节点每小时流量数据日志
 
-            NodeHeartbeat::where('log_time', '<=', strtotime(config('tasks.clean.node_heartbeats')))->delete(); // 清除节点负载信息日志
+            NodeHeartbeat::where('log_time', '<=', strtotime(sysConfig('tasks_clean.node_heartbeats')))->delete(); // 清除节点负载信息日志
 
-            NodeOnlineLog::where('log_time', '<=', strtotime(config('tasks.clean.node_online_logs')))->delete(); // 清除节点在线用户数日志
+            NodeOnlineLog::where('log_time', '<=', strtotime(sysConfig('tasks_clean.node_online_logs')))->delete(); // 清除节点在线用户数日志
 
-            RuleLog::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.rule_logs'))))->delete(); // 清理审计触发日志
+            RuleLog::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.rule_logs'))))->delete(); // 清理审计触发日志
 
-            NodeOnlineIp::where('created_at', '<=', strtotime(config('tasks.clean.node_online_ips')))->delete(); // 清除用户连接IP
+            NodeOnlineIp::where('created_at', '<=', strtotime(sysConfig('tasks_clean.node_online_ips')))->delete(); // 清除用户连接IP
 
             UserDailyDataFlow::where(static function (Builder $query) {
-                $query->where('node_id', '<>', null)->where('created_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.user_daily_logs_nodes'))));
-            })->orWhere('created_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.user_daily_logs_total'))))->delete(); // 清除用户各节点 / 节点总计的每天流量数据日志
+                $query->where('node_id', '<>', null)->where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.user_daily_logs_nodes'))));
+            })->orWhere('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.user_daily_logs_total'))))->delete(); // 清除用户各节点 / 节点总计的每天流量数据日志
 
-            UserHourlyDataFlow::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.user_hourly_logs'))))->delete(); // 清除用户每时各流量数据日志
+            UserHourlyDataFlow::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.user_hourly_logs'))))->delete(); // 清除用户每时各流量数据日志
 
-            UserSubscribeLog::where('request_time', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.subscribe_logs'))))->delete(); // 清理用户订阅请求日志
+            UserSubscribeLog::where('request_time', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.subscribe_logs'))))->delete(); // 清理用户订阅请求日志
 
-            UserDataFlowLog::where('log_time', '<=', strtotime(config('tasks.clean.traffic_logs')))->delete(); // 清除用户流量日志
+            UserDataFlowLog::where('log_time', '<=', strtotime(sysConfig('tasks_clean.traffic_logs')))->delete(); // 清除用户流量日志
         } catch (Exception $e) {
             Log::emergency(trans('common.error_item', ['attribute' => trans('admin.system.is_clear_log')]).': '.$e->getMessage());
         }

+ 1 - 1
app/Console/Commands/ServiceTimer.php

@@ -24,7 +24,7 @@ class ServiceTimer extends Command
 
     private function expiredPlan(): void
     {
-        Order::activePlan()->where('expired_at', '<=', now())->chunk(config('tasks.chunk'), function ($orders) {
+        Order::activePlan()->where('expired_at', '<=', now())->chunk(sysConfig('tasks_chunk'), function ($orders) {
             $orders->each->expired(); // 过期订单
         });
     }

+ 12 - 12
app/Console/Commands/TaskAuto.php

@@ -29,7 +29,7 @@ class TaskAuto extends Command
 
         $this->orderTimer(); // 超时订单
         $this->expireCode(); // 过期验证码、优惠券、邀请码无效化
-        if (sysConfig('is_subscribe_ban')) {
+        if (sysConfig('subscribe_rate_limit')) {
             $this->blockSubscribes(); // 封禁访问异常的订阅
         }
         $this->unblockSubscribes(); // 解禁订阅
@@ -49,8 +49,8 @@ class TaskAuto extends Command
         Order::where(function (Builder $query) {
             $query->recentUnPay(); // 关闭超时未支付本地订单
         })->orWhere(function (Builder $query) {
-            $query->whereStatus(1)->where('created_at', '<=', date('Y-m-d H:i:s', strtotime('-'.config('tasks.close.confirmation_orders').' hours'))); // 关闭未处理的人工支付订单
-        })->chunk(config('tasks.chunk'), function ($orders) {
+            $query->whereStatus(1)->where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_close.confirmation_orders')))); // 关闭未处理的人工支付订单
+        })->chunk(sysConfig('tasks_chunk'), function ($orders) {
             $orders->each->close();
         });
     }
@@ -69,7 +69,7 @@ class TaskAuto extends Command
 
     private function blockSubscribes(): void
     { // 封禁访问异常地订阅链接
-        $trafficBanTime = sysConfig('traffic_ban_time');
+        $trafficBanTime = sysConfig('ban_duration');
         $ban_time = strtotime($trafficBanTime.' minutes');
         $dirtyWorks = ['status' => 0, 'ban_time' => $ban_time, 'ban_desc' => 'Subscription link receive abnormal access and banned by the system'];
         $banMsg = ['time' => $trafficBanTime, 'description' => __('[Auto Task] Blocked Subscription: Subscription with abnormal requests within 24 hours')];
@@ -78,7 +78,7 @@ class TaskAuto extends Command
             $query->whereStatus(1); // 获取有订阅且未被封禁用户
         })->whereHas('subscribeLogs', function (Builder $query) {
             $query->whereDate('request_time', '>=', now()->subDay()); //    ->distinct()->count('request_ip');
-        }, '>=', sysConfig('subscribe_ban_times'))->chunk(config('tasks.chunk'), function ($users) use ($banMsg, $dirtyWorks) {
+        }, '>=', sysConfig('subscribe_rate_limit'))->chunk(sysConfig('tasks_chunk'), function ($users) use ($banMsg, $dirtyWorks) {
             foreach ($users as $user) {
                 $user->subscribe->update($dirtyWorks);
                 $user->banedLogs()->create($banMsg); // 记录封禁日志
@@ -88,7 +88,7 @@ class TaskAuto extends Command
 
     private function unblockSubscribes(): void
     {
-        UserSubscribe::whereStatus(0)->where('ban_time', '<=', time())->chunk(config('tasks.chunk'), function ($subscribes) {
+        UserSubscribe::whereStatus(0)->where('ban_time', '<=', time())->chunk(sysConfig('tasks_chunk'), function ($subscribes) {
             $subscribes->each->update(['status' => 1, 'ban_time' => null, 'ban_desc' => null]);
         });
     }
@@ -96,7 +96,7 @@ class TaskAuto extends Command
     private function blockUsers(): void
     { // 封禁账号
         // 禁用流量超限用户
-        User::activeUser()->whereRaw('u + d >= transfer_enable')->chunk(config('tasks.chunk'), function ($users) {
+        User::activeUser()->whereRaw('u + d >= transfer_enable')->chunk(sysConfig('tasks_chunk'), function ($users) {
             $users->each(function ($user) {
                 $user->update(['enable' => 0]);
                 $user->banedLogs()->create(['description' => __('[Auto Task] Blocked service: Run out of traffic')]);
@@ -104,13 +104,13 @@ class TaskAuto extends Command
         });
 
         // 封禁1小时内流量异常账号
-        if (sysConfig('is_traffic_ban')) {
-            $trafficBanTime = sysConfig('traffic_ban_time');
+        if (sysConfig('traffic_abuse_limit')) {
+            $trafficBanTime = sysConfig('ban_duration');
             $ban_time = strtotime($trafficBanTime.' minutes');
             $userService = new UserService;
 
             User::activeUser()->whereBanTime(null)->where('t', '>=', strtotime('-5 minutes')) // 只检测最近5分钟有流量使用的用户
-                ->chunk(config('tasks.chunk'), function ($users) use ($userService, $ban_time, $trafficBanTime) {
+                ->chunk(sysConfig('tasks_chunk'), function ($users) use ($userService, $ban_time, $trafficBanTime) {
                     $users->each(function ($user) use ($userService, $ban_time, $trafficBanTime) {
                         $userService->setUser($user);
                         if ($userService->isTrafficWarning()) {
@@ -125,7 +125,7 @@ class TaskAuto extends Command
     private function unblockUsers(): void
     { // 解封账号
         // 解封被临时封禁的账号
-        User::bannedUser()->where('ban_time', '<', time())->chunk(config('tasks.chunk'), function ($users) {
+        User::bannedUser()->where('ban_time', '<', time())->chunk(sysConfig('tasks_chunk'), function ($users) {
             $users->each(function ($user) {
                 $user->update(['enable' => 1, 'ban_time' => null]);
                 $user->banedLogs()->create(['description' => __('[Auto Task] Unblocked Service: Account ban expired')]);
@@ -133,7 +133,7 @@ class TaskAuto extends Command
         });
 
         // 可用流量大于已用流量也解封(比如:邀请返利加了流量)
-        User::bannedUser()->whereBanTime(null)->where('expired_at', '>=', date('Y-m-d'))->whereRaw('u + d < transfer_enable')->chunk(config('tasks.chunk'), function ($users) {
+        User::bannedUser()->whereBanTime(null)->where('expired_at', '>=', date('Y-m-d'))->whereRaw('u + d < transfer_enable')->chunk(sysConfig('tasks_chunk'), function ($users) {
             $users->each(function ($user) {
                 $user->update(['enable' => 1]);
                 $user->banedLogs()->create(['description' => __('[Auto Task] Unblocked Service: Account has available data traffic')]);

+ 7 - 8
app/Console/Commands/TaskDaily.php

@@ -60,7 +60,7 @@ class TaskDaily extends Command
         }
 
         User::activeUser()->where('expired_at', '<', date('Y-m-d')) // 过期
-            ->chunk(config('tasks.chunk'), function ($users) use ($banMsg, $dirtyWorks) {
+            ->chunk(sysConfig('tasks_chunk'), function ($users) use ($banMsg, $dirtyWorks) {
                 $users->each(function ($user) use ($banMsg, $dirtyWorks) {
                     $user->update($dirtyWorks);
                     Helpers::addUserTrafficModifyLog($user->id, $user->transfer_enable, 0, $banMsg);
@@ -71,11 +71,10 @@ class TaskDaily extends Command
 
     private function closeTickets(): void
     { // 关闭用户超时未处理的工单
-        $closeTicketsHours = config('tasks.close.tickets');
-
+        $closeTicketsHours = (time() - strtotime(sysConfig('tasks_close.tickets'))) / 3600;
         Ticket::whereStatus(1)->with('reply')->whereHas('reply', function ($query) {
             $query->where('admin_id', '<>', null);
-        })->where('updated_at', '<=', now()->subHours($closeTicketsHours))->chunk(config('tasks.chunk'), function ($tickets) use ($closeTicketsHours) {
+        })->where('updated_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_close.tickets'))))->chunk(sysConfig('tasks_chunk'), function ($tickets) use ($closeTicketsHours) {
             $tickets->each(function ($ticket) use ($closeTicketsHours) {
                 if ($ticket->close()) {
                     $ticket->user->notify(new TicketClosed($ticket->id, $ticket->title, route('ticket.edit', $ticket),
@@ -92,7 +91,7 @@ class TaskDaily extends Command
             $query->activePlan();
         })->with(['orders' => function ($query) {
             $query->activePlan();
-        }])->chunk(config('tasks.chunk'), function ($users) {
+        }])->chunk(sysConfig('tasks_chunk'), function ($users) {
             $users->each(function ($user) {
                 $user->orders()->activePackage()->update(['is_expire' => 1]); // 过期生效中的加油包
                 $order = $user->orders->first(); // 取出用户正在使用的套餐
@@ -111,7 +110,7 @@ class TaskDaily extends Command
     private function releaseAccountPort(): void
     { // 被封禁 / 过期N天 的账号自动释放端口
         User::where('port', '<>', 0)->where(function (Builder $query) {
-            $query->whereStatus(-1)->orWhere('expired_at', '<=', date('Y-m-d', strtotime('-'.config('tasks.release_port').' days')));
+            $query->whereStatus(-1)->orWhere('expired_at', '<=', date('Y-m-d', strtotime('-'.sysConfig('auto_release_port').' days')));
         })->update(['port' => 0]);
     }
 
@@ -127,7 +126,7 @@ class TaskDaily extends Command
             'dataFlowLogs' => function ($query) use ($start, $end) {
                 $query->whereBetween('log_time', [$start, $end]);
             },
-        ])->chunk(config('tasks.chunk'), function ($users) use ($created_at) {
+        ])->chunk(sysConfig('tasks_chunk'), function ($users) use ($created_at) {
             foreach ($users as $user) {
                 $dataFlowLogs = $user->dataFlowLogs->groupBy('node_id');
 
@@ -175,7 +174,7 @@ class TaskDaily extends Command
             'userDataFlowLogs as d_sum' => function ($query) use ($start, $end) {
                 $query->select(DB::raw('SUM(d)'))->whereBetween('log_time', [$start, $end]);
             },
-        ])->chunk(config('tasks.chunk'), function ($nodes) use ($created_at) {
+        ])->chunk(sysConfig('tasks_chunk'), function ($nodes) use ($created_at) {
             foreach ($nodes as $node) {
                 $node->dailyDataFlows()->create([
                     'u' => $node->u_sum,

+ 4 - 4
app/Console/Commands/TaskHourly.php

@@ -34,14 +34,14 @@ class TaskHourly extends Command
         $end = strtotime($created_at);
         $start = $end - 3599;
         $data_anomaly_notification = sysConfig('data_anomaly_notification');
-        $traffic_ban_value = (int) sysConfig('traffic_ban_value') * GiB;
+        $traffic_abuse_threshold = (int) sysConfig('traffic_abuse_limit') * GiB;
         User::activeUser()->whereHas('dataFlowLogs', function (Builder $query) use ($start, $end) {
             $query->whereBetween('log_time', [$start, $end]);
         })->with([
             'dataFlowLogs' => function ($query) use ($start, $end) {
                 $query->whereBetween('log_time', [$start, $end]);
             },
-        ])->chunk(config('tasks.chunk'), function ($users) use ($traffic_ban_value, $created_at, $data_anomaly_notification) {
+        ])->chunk(sysConfig('tasks_chunk'), function ($users) use ($traffic_abuse_threshold, $created_at, $data_anomaly_notification) {
             foreach ($users as $user) {
                 $dataFlowLogs = $user->dataFlowLogs->groupBy('node_id');
 
@@ -74,7 +74,7 @@ class TaskHourly extends Command
                 $sum_all = $sum_u + $sum_d;
 
                 // 用户流量异常警告
-                if ($data_anomaly_notification && $sum_all >= $traffic_ban_value) {
+                if ($data_anomaly_notification && $sum_all >= $traffic_abuse_threshold) {
                     Notification::send(User::find(1), new DataAnomaly($user->id, formatBytes($sum_u), formatBytes($sum_d), formatBytes($sum_all)));
                 }
             }
@@ -97,7 +97,7 @@ class TaskHourly extends Command
             'userDataFlowLogs as d_sum' => function ($query) use ($start, $end) {
                 $query->select(DB::raw('SUM(d)'))->whereBetween('log_time', [$start, $end]);
             },
-        ])->chunk(config('tasks.chunk'), function ($nodes) use ($created_at) {
+        ])->chunk(sysConfig('tasks_chunk'), function ($nodes) use ($created_at) {
             foreach ($nodes as $node) {
                 $node->hourlyDataFlows()->create(['u' => $node->u_sum, 'd' => $node->d_sum, 'created_at' => $created_at]);
             }

+ 5 - 5
app/Console/Commands/TaskMonthly.php

@@ -42,15 +42,15 @@ class TaskMonthly extends Command
     private function clearLog(): void
     {
         try {
-            NotificationLog::where('updated_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.notification_logs'))))->delete(); // 清理通知日志
+            NotificationLog::where('updated_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.notification_logs'))))->delete(); // 清理通知日志
 
-            UserLoginLog::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.login_logs'))))->delete(); // 清除用户登陆日志
+            UserLoginLog::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.login_logs'))))->delete(); // 清除用户登陆日志
 
-            Payment::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.payments'))))->delete(); // 清理在线支付日志
+            Payment::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.payments'))))->delete(); // 清理在线支付日志
 
-            UserBanedLog::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.user_baned_logs'))))->delete(); // 清理用户封禁日志
+            UserBanedLog::where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.user_baned_logs'))))->delete(); // 清理用户封禁日志
 
-            Order::whereStatus(-1)->where('created_at', '<=', date('Y-m-d H:i:s', strtotime(config('tasks.clean.unpaid_orders'))))->delete(); // 清理用户未支付订单
+            Order::whereStatus(-1)->where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_clean.unpaid_orders'))))->delete(); // 清理用户未支付订单
         } catch (Exception $e) {
             Log::emergency(trans('common.error_item', ['attribute' => trans('admin.system.is_clear_log')]).': '.$e->getMessage());
         }

+ 1 - 1
app/Console/Commands/UserExpireWarning.php

@@ -30,7 +30,7 @@ class UserExpireWarning extends Command
         // 只取没被禁用的用户,其他不用管
         User::whereEnable(1)
             ->where('expired_at', '<', date('Y-m-d', strtotime(sysConfig('expire_days').' days')))
-            ->chunk(config('tasks.chunk'), function ($users) {
+            ->chunk(sysConfig('tasks_chunk'), function ($users) {
                 foreach ($users as $user) {
                     if (filter_var($user->username, FILTER_VALIDATE_EMAIL) === false) { // 用户账号不是邮箱的跳过
                         continue;

+ 1 - 1
app/Console/Commands/UserTrafficWarning.php

@@ -28,7 +28,7 @@ class UserTrafficWarning extends Command
     private function userTrafficWarning(): void
     { // 用户流量超过警告阈值提醒
         $trafficWarningPercent = sysConfig('traffic_warning_percent');
-        User::activeUser()->where('transfer_enable', '>', 0)->chunk(config('tasks.chunk'), function ($users) use ($trafficWarningPercent) {
+        User::activeUser()->where('transfer_enable', '>', 0)->chunk(sysConfig('tasks_chunk'), function ($users) use ($trafficWarningPercent) {
             foreach ($users as $user) {
                 // 用户账号不是邮箱的跳过
                 if (filter_var($user->username, FILTER_VALIDATE_EMAIL) === false) {

+ 2 - 1
app/Http/Controllers/Admin/LogsController.php

@@ -15,6 +15,7 @@ use App\Models\UserCreditLog;
 use App\Models\UserDataFlowLog;
 use App\Models\UserDataModifyLog;
 use App\Utils\IP;
+use App\Utils\Payments\PaymentManager;
 use Illuminate\Contracts\View\View;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
@@ -63,7 +64,7 @@ class LogsController extends Controller
             });
         }
 
-        return view('admin.logs.order', ['orders' => $query->sortable(['id' => 'desc'])->paginate(15)->appends($request->except('page'))]);
+        return view('admin.logs.order', ['orders' => $query->sortable(['id' => 'desc'])->paginate(15)->appends($request->except('page')), 'paymentLabels' => PaymentManager::getLabels(true)]);
     }
 
     public function changeOrderStatus(Request $request): JsonResponse

+ 0 - 6
app/Http/Controllers/Admin/MarketingController.php

@@ -50,12 +50,6 @@ class MarketingController extends Controller
         $content = $request->input('content');
 
         if ($type === 'push') {
-            //            if (! sysConfig('is_push_bear')) {
-            //                return response()->json(['status' => 'fail', 'message' => '推送失败:请先启用并配置PushBear']);
-            //            }
-            //
-            //            Notification::send(PushBearChannel::class, new Custom($title, $content));
-            //            return response()->json(['status' => 'success', 'message' => '发送完成']);
             return response()->json(['status' => 'fail', 'message' => trans('common.developing')]);
         }
 

+ 1 - 1
app/Http/Controllers/Admin/NodeController.php

@@ -33,7 +33,7 @@ class NodeController extends Controller
                     $query->where('log_time', '>=', strtotime('-5 minutes'))->orderBy('log_time', 'desc');
                 },
                 'heartbeats' => function ($query) {
-                    $query->where('log_time', '>=', strtotime(config('tasks.recently_heartbeat')))->orderBy('log_time', 'desc');
+                    $query->where('log_time', '>=', strtotime('-'.sysConfig('recently_heartbeat').' minutes'))->orderBy('log_time', 'desc');
                 },
                 'childNodes',
             ])

+ 1 - 1
app/Http/Controllers/Admin/SubscribeController.php

@@ -60,7 +60,7 @@ class SubscribeController extends Controller
     public function setSubscribeStatus(UserSubscribe $userSubscribe): JsonResponse
     {
         $data = $userSubscribe->status
-            ? ['status' => 0, 'ban_time' => strtotime(sysConfig('traffic_ban_time').' minutes'), 'ban_desc' => 'Your subscription has been disabled by the administrator, please contact the administrator to restore it']
+            ? ['status' => 0, 'ban_time' => strtotime(sysConfig('ban_duration').' minutes'), 'ban_desc' => 'Your subscription has been disabled by the administrator, please contact the administrator to restore it']
             : ['status' => 1, 'ban_time' => null, 'ban_desc' => null];
 
         $ret = $userSubscribe->update($data)

+ 62 - 37
app/Http/Controllers/Admin/SystemController.php

@@ -21,6 +21,7 @@ use App\Models\SsConfig;
 use App\Notifications\Custom;
 use App\Services\TelegramService;
 use App\Utils\DDNS;
+use App\Utils\Payments\PaymentManager;
 use Illuminate\Contracts\View\View;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\RedirectResponse;
@@ -32,42 +33,48 @@ class SystemController extends Controller
 {
     public function index(): View
     { // 系统设置
-        return view('admin.config.system', array_merge([
-            'payments' => $this->getPayments(),
-            'captcha' => $this->getCaptcha(),
-            'channels' => $this->getNotifyChannels(),
-            'ddns_labels' => (new DDNS)->getLabels(),
-        ], Config::pluck('value', 'name')->toArray()));
-    }
-
-    private function getPayments(): array
-    {
-        $paymentConfigs = cache()->rememberForever('payment_configs', function () { // 支付渠道及其所需配置项映射
-            foreach (glob(app_path('Utils/Payments/*.php')) as $file) {
-                $className = 'App\\Utils\\Payments\\'.basename($file, '.php');
-                if (class_exists($className)) {
-                    $methodDetails = $className::$methodDetails ?? null;
-                    if ($methodDetails && ! empty($methodDetails['settings'])) {
-                        $configs[$methodDetails['key']] = $methodDetails['settings'];
-                    }
-                }
+        function parseTime(string|array $inputs): array
+        {
+            $items = is_array($inputs) ? $inputs : json_decode($inputs, true);
+            $result = [];
+
+            foreach ($items as $key => $duration) {
+                preg_match('/-(\d+)\s+(\w+)/', $duration, $m);
+                $result[$key] = [
+                    'num' => $m[1] ?? 1,
+                    'unit' => $m[2] ?? 'hours',
+                ];
             }
 
-            return $configs ?? [];
-        });
-
-        // 遍历映射,检查配置项是否存在
-        foreach ($paymentConfigs as $paymentName => $configKeys) {
-            $allConfigsExist = array_reduce($configKeys, static function ($carry, $configKey) {
-                return $carry && sysConfig($configKey);
-            }, true);
-
-            if ($allConfigsExist) {
-                $payment[] = $paymentName;
-            }
+            return $result;
         }
 
-        return $payment ?? [];
+        // 获取所有配置项
+        $config = Config::pluck('value', 'name')->toArray();
+
+        // 预处理复杂数据
+        // 解析时间类配置
+        $config['tasks_clean'] = parseTime($config['tasks_clean']);
+        $config['tasks_close'] = parseTime($config['tasks_close']);
+
+        $paymentForms = PaymentManager::getSettingsFormData();
+
+        return view('admin.config.system', [
+            'configs' => $config,
+            'payments' => PaymentManager::getAvailable(),
+            'paymentForms' => $paymentForms,
+            'paymentTabs' => array_keys($paymentForms),
+            'paymentLists' => [
+                'ali' => PaymentManager::getPaymentsByMethod('ali'),
+                'wechat' => PaymentManager::getPaymentsByMethod('wechat'),
+                'qq' => PaymentManager::getPaymentsByMethod('qq'),
+                'other' => PaymentManager::getPaymentsByMethod('other'),
+            ],
+            'captcha' => $this->getCaptcha(),
+            'notifies' => $this->getNotifyChannels(),
+            'notifyForms' => $this->getNotifyForms(),
+            'ddns_labels' => (new DDNS)->getLabels(),
+        ]);
     }
 
     private function getCaptcha(): bool
@@ -77,7 +84,7 @@ class SystemController extends Controller
 
     private function getNotifyChannels(): array
     {
-        $configs = [ // 支付渠道及其所需配置项映射
+        $configMap = [ // 支付渠道及其所需配置项映射
             'bark' => ['bark_key'],
             'dingTalk' => ['dingTalk_access_token'],
             'iYuu' => ['iYuu_token'],
@@ -92,7 +99,7 @@ class SystemController extends Controller
         $channels = ['database', 'mail'];
 
         // 遍历映射,检查配置项是否存在
-        foreach ($configs as $channel => $configKeys) {
+        foreach ($configMap as $channel => $configKeys) {
             $allConfigsExist = array_reduce($configKeys, static function ($carry, $configKey) {
                 return $carry && sysConfig($configKey);
             }, true);
@@ -105,6 +112,26 @@ class SystemController extends Controller
         return $channels;
     }
 
+    private function getNotifyForms(): array
+    {
+        return [
+            'server_chan_key' => ['test' => 'serverChan'],
+            'pushDeer_key' => ['test' => 'pushDeer'],
+            'iYuu_token' => ['test' => 'iYuu'],
+            'bark_key' => ['test' => 'bark'],
+            'telegram_token' => ['test' => 'telegram'],
+            'pushplus_token' => ['test' => 'pushPlus'],
+            'dingTalk_access_token' => null,
+            'dingTalk_secret' => ['test' => 'dingTalk'],
+            'wechat_cid' => null,
+            'wechat_aid' => null,
+            'wechat_secret' => ['test' => 'weChat'],
+            'wechat_token' => ['url' => route('wechat.verify')],
+            'wechat_encodingAESKey' => null,
+            'tg_chat_token' => ['test' => 'tgChat'],
+        ];
+    }
+
     public function setExtend(Request $request): RedirectResponse  // 设置涉及到上传的设置
     {
         if ($request->hasAny(['website_home_logo', 'website_home_logo'])) { // 首页LOGO
@@ -179,7 +206,7 @@ class SystemController extends Controller
         }
 
         // 支付设置判断
-        if ($value !== null && in_array($name, ['is_AliPay', 'is_QQPay', 'is_WeChatPay'], true) && ! in_array($value, $this->getPayments(), true)) {
+        if ($value !== null && in_array($name, ['is_AliPay', 'is_QQPay', 'is_WeChatPay'], true) && ! in_array($value, PaymentManager::getAvailable(), true)) {
             return response()->json(['status' => 'fail', 'message' => trans('admin.system.params_required', ['attribute' => trans('admin.system.payment.attribute')])]);
         }
 
@@ -192,8 +219,6 @@ class SystemController extends Controller
             $denyConfig = [
                 'website_url',
                 'is_captcha',
-                'min_rand_traffic',
-                'max_rand_traffic',
                 'forbid_mode',
                 'website_security_code',
                 'website_security_code',

+ 5 - 5
app/Http/Controllers/Api/Client/ClientController.php

@@ -153,7 +153,7 @@ class ClientController extends Controller
     {
         $user = $request->user();
         // 系统开启登录加积分功能才可以签到
-        if (! sysConfig('is_checkin')) {
+        if (! sysConfig('checkin_interval')) {
             return response()->json(['ret' => 0, 'title' => trans('common.failed'), 'msg' => trans('user.home.attendance.disable')]);
         }
 
@@ -162,7 +162,7 @@ class ClientController extends Controller
             return response()->json(['ret' => 0, 'title' => trans('common.success'), 'msg' => trans('user.home.attendance.done')]);
         }
 
-        $traffic = random_int((int) sysConfig('min_rand_traffic'), (int) sysConfig('max_rand_traffic')) * MiB;
+        $traffic = random_int((int) sysConfig('checkin_reward'), (int) sysConfig('checkin_reward_max')) * MiB;
 
         if (! $user->incrementData($traffic)) {
             return response()->json(['ret' => 0, 'title' => trans('common.failed'), 'msg' => trans('user.home.attendance.failed')]);
@@ -170,7 +170,7 @@ class ClientController extends Controller
         Helpers::addUserTrafficModifyLog($user->id, $user->transfer_enable, $user->transfer_enable + $traffic, trans('user.home.attendance.attribute'));
 
         // 多久后可以再签到
-        $ttl = sysConfig('traffic_limit_time') ? sysConfig('traffic_limit_time') * Minute : Day;
+        $ttl = sysConfig('checkin_interval') ? sysConfig('checkin_interval') * Minute : Day;
         Cache::put('userCheckIn_'.$user->id, '1', $ttl);
 
         return $this->succeed(null, null, [200, trans('user.home.attendance.success', ['data' => formatBytes($traffic)])]);
@@ -264,8 +264,8 @@ class ClientController extends Controller
             'client.node_class_name' => Level::all()->pluck('name', 'level')->toArray(),
             'client.baseUrl' => sysConfig('website_url'),
             'client.subscribe_url' => sysConfig('subscribe_domain') ?: sysConfig('website_url'),
-            'client.checkinMin' => sysConfig('min_rand_traffic'),
-            'client.checkinMax' => sysConfig('max_rand_traffic'),
+            'client.checkinMin' => sysConfig('checkin_reward'),
+            'client.checkinMax' => sysConfig('checkin_reward_max'),
             'client.invite_gift' => sysConfig('default_traffic') / 1024,
         ]);
     }

+ 1 - 1
app/Http/Controllers/AuthController.php

@@ -331,7 +331,7 @@ class AuthController extends Controller
         if (is_numeric($aff)) {
             $uid = (int) $aff;
         } else {
-            $decode = (new Hashids(sysConfig('aff_salt'), 8))->decode($aff);
+            $decode = (new Hashids(sysConfig('affiliate_link_salt'), 8))->decode($aff);
             if ($decode) {
                 $uid = $decode[0];
             }

+ 3 - 3
app/Http/Controllers/UserController.php

@@ -53,7 +53,7 @@ class UserController extends Controller
     {
         $user = auth()->user();
         // 系统开启登录加积分功能才可以签到
-        if (! sysConfig('is_checkin')) {
+        if (! sysConfig('checkin_interval')) {
             return response()->json(['status' => 'fail', 'title' => trans('common.failed'), 'message' => trans('user.home.attendance.disable')]);
         }
 
@@ -62,14 +62,14 @@ class UserController extends Controller
             return response()->json(['status' => 'success', 'title' => trans('common.success'), 'message' => trans('user.home.attendance.done')]);
         }
 
-        $traffic = random_int((int) sysConfig('min_rand_traffic'), (int) sysConfig('max_rand_traffic')) * MiB;
+        $traffic = random_int((int) sysConfig('checkin_reward'), (int) sysConfig('checkin_reward_max')) * MiB;
 
         if (! $user->incrementData($traffic)) {
             return response()->json(['status' => 'fail', 'title' => trans('common.failed'), 'message' => trans('user.home.attendance.failed')]);
         }
         Helpers::addUserTrafficModifyLog($user->id, $user->transfer_enable, $user->transfer_enable + $traffic, trans('user.home.attendance.attribute'));
 
-        cache()->put('userCheckIn_'.$user->id, '1', sysConfig('traffic_limit_time') ? sysConfig('traffic_limit_time') * Minute : Day); // 多久后可以再签到
+        cache()->put('userCheckIn_'.$user->id, '1', sysConfig('checkin_interval') ? sysConfig('checkin_interval') * Minute : Day); // 多久后可以再签到
 
         return response()->json(['status' => 'success', 'message' => trans('user.home.attendance.success', ['data' => formatBytes($traffic)])]);
     }

+ 1 - 1
app/Models/NodeHeartbeat.php

@@ -18,6 +18,6 @@ class NodeHeartbeat extends Model
 
     public function scopeRecently(Builder $query): Builder
     {
-        return $query->where('log_time', '>=', strtotime(config('tasks.recently_heartbeat')))->latest('log_time');
+        return $query->where('log_time', '>=', strtotime('-'.sysConfig('recently_heartbeat').' minutes'))->latest('log_time');
     }
 }

+ 6 - 9
app/Models/Order.php

@@ -5,6 +5,7 @@ namespace App\Models;
 use App\Casts\money;
 use App\Observers\OrderObserver;
 use App\Utils\Helpers;
+use App\Utils\Payments\PaymentManager;
 use Auth;
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
 use Illuminate\Database\Eloquent\Builder;
@@ -54,13 +55,9 @@ class Order extends Model
         return $query->whereUserId($uid ?? Auth::id());
     }
 
-    public function scopeRecentUnPay(Builder $query, int $minutes = 0): Builder
+    public function scopeRecentUnPay(Builder $query): Builder
     {
-        if (! $minutes) {
-            $minutes = (int) config('tasks.close.orders');
-        }
-
-        return $query->whereStatus(0)->where('created_at', '<=', date('Y-m-d H:i:s', strtotime("-$minutes minutes")));
+        return $query->whereStatus(0)->where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_close.orders'))));
     }
 
     public function scopeUserPrepay(Builder $query, ?int $uid = null): Builder
@@ -191,8 +188,8 @@ class Order extends Model
             2 => trans('common.payment.qq'),
             3 => trans('common.payment.wechat'),
             4 => trans('common.payment.crypto'),
-            5 => 'PayPal',
-            6 => 'Stripe',
+            5 => trans('admin.system.payment.channel.paypal'),
+            6 => trans('admin.system.payment.channel.stripe'),
             7 => trans('common.payment.manual'),
             default => '',
         };
@@ -207,6 +204,6 @@ class Order extends Model
     // 支付方式
     public function getPayWayLabelAttribute(): string
     {
-        return config('common.payment.labels')[$this->pay_way] ?? trans('common.status.unknown');
+        return PaymentManager::getLabels('true')[$this->pay_way] ?? trans('common.status.unknown');
     }
 }

+ 1 - 1
app/Models/User.php

@@ -224,7 +224,7 @@ class User extends Authenticatable
     public function getInviteCodeAttribute(): string
     {
         $uid = $this->id;
-        $affSalt = sysConfig('aff_salt');
+        $affSalt = sysConfig('affiliate_link_salt');
 
         return empty($affSalt) ? $uid : (new Hashids($affSalt, 8))->encode($uid);
     }

+ 1 - 1
app/Models/UserHourlyDataFlow.php

@@ -52,7 +52,7 @@ class UserHourlyDataFlow extends Model
             ->selectRaw('user_id, sum(u + d) as total')->pluck('total', 'user_id')
             ->toArray(); // 只统计10M以上的记录,加快速度
         foreach ($userTotalTrafficList as $user => $traffic) {
-            if ($traffic > (int) sysConfig('traffic_ban_value') * GiB) {
+            if ($traffic > (int) sysConfig('traffic_abuse_limit') * GiB) {
                 $result[] = $user;
             }
         }

+ 1 - 1
app/Models/VerifyCode.php

@@ -16,6 +16,6 @@ class VerifyCode extends Model
 
     public function scopeRecentUnused(Builder $query): Builder
     {
-        return $query->whereStatus(0)->where('created_at', '<=', date('Y-m-d H:i:s', strtotime('-'.config('tasks.close.verify').' minutes')));
+        return $query->whereStatus(0)->where('created_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_close.verify'))));
     }
 }

+ 1 - 1
app/Notifications/Verification.php

@@ -28,7 +28,7 @@ class Verification extends Notification
             ->subject(trans('notification.verification_account'))
             ->line(trans('notification.verification'))
             ->line($this->code)
-            ->line(trans('notification.verification_limit', ['minutes' => config('tasks.close.verify')]));
+            ->line(trans('notification.verification_limit', ['minutes' => (time() - strtotime(sysConfig('tasks_close.verify'))) / 60]));
     }
 
     public function toArray($notifiable): array

+ 2 - 0
app/Providers/SettingServiceProvider.php

@@ -55,6 +55,8 @@ class SettingServiceProvider extends ServiceProvider
             ->merge(collect(['is_onlinePay' => $settings->whereIn('name', $payments)->pluck('value')->filter()->isNotEmpty()])) // 设置在线支付开关
             ->sortKeys()
             ->toArray();
+        $modified['tasks_clean'] = json_decode($modified['tasks_clean'], true);
+        $modified['tasks_close'] = json_decode($modified['tasks_close'], true);
 
         config(['settings' => $modified]); // 设置系统参数
 

+ 1 - 1
app/Services/OrderService.php

@@ -131,7 +131,7 @@ class OrderService
 
     private function setCommissionExpense(User $user): void
     { // 佣金计算
-        $referralType = sysConfig('referral_type');
+        $referralType = sysConfig('referral_reward_type');
 
         if ($referralType && $user->inviter_id) {// 是否需要支付佣金
             $inviter = $user->inviter;

+ 1 - 1
app/Services/UserService.php

@@ -82,7 +82,7 @@ class UserService
 
     public function isTrafficWarning(): bool
     { // 流量异常警告
-        return ((int) sysConfig('traffic_ban_value') * GiB) <= $this->recentTrafficUsed();
+        return ((int) sysConfig('traffic_abuse_limit') * GiB) <= $this->recentTrafficUsed();
     }
 
     public function recentTrafficUsed(): int

+ 1 - 1
app/Utils/Library/AlipayF2F.php

@@ -14,7 +14,7 @@ use RuntimeException;
 
 class AlipayF2F
 {
-    private static string $gatewayUrl = 'https://openapi.alipay.com/gateway.do'; //https://openapi-sandbox.dl.alipaydev.com/gateway.do
+    private static string $gatewayUrl = 'https://openapi.alipay.com/gateway.do'; // https://openapi-sandbox.dl.alipaydev.com/gateway.do
 
     private array $config;
 

+ 2 - 0
app/Utils/Library/Templates/Gateway.php

@@ -7,6 +7,8 @@ use Illuminate\Http\Request;
 
 interface Gateway
 {
+    public static function metadata(): array;
+
     public function purchase(Request $request): JsonResponse;
 
     public function notify(Request $request);

+ 17 - 7
app/Utils/Payments/CodePay.php

@@ -10,10 +10,21 @@ use Log;
 
 class CodePay implements Gateway
 {
-    public static array $methodDetails = [
-        'key' => 'codepay',
-        'settings' => ['codepay_url', 'codepay_id', 'codepay_key'],
-    ];
+    public static function metadata(): array
+    {
+        return [
+            'key' => 'codepay',
+            'method' => ['ali', 'qq', 'wechat'],
+            'settings' => [
+                'codepay_url' => [
+                    'type' => 'url',
+                    'placeholder' => 'admin.system.placeholder.codepay_url',
+                ],
+                'codepay_id' => null,
+                'codepay_key' => null,
+            ],
+        ];
+    }
 
     public function purchase(Request $request): JsonResponse
     {
@@ -22,7 +33,7 @@ class CodePay implements Gateway
         $data = [
             'id' => sysConfig('codepay_id'),
             'pay_id' => $payment->trade_no,
-            'type' => $request->input('type'), //1支付宝支付 2QQ钱包 3微信支付
+            'type' => $request->input('type'), // 1支付宝支付 2QQ钱包 3微信支付
             'price' => $payment->amount,
             'page' => 1,
             'outTime' => 900,
@@ -40,8 +51,7 @@ class CodePay implements Gateway
     public function notify(Request $request): void
     {
         $tradeNo = $request->input('pay_id');
-        if ($tradeNo && $request->input('pay_no')
-            && PaymentHelper::verify($request->except('method'), sysConfig('codepay_key'), $request->input('sign'), false)) {
+        if ($tradeNo && $request->input('pay_no') && PaymentHelper::verify($request->except('method'), sysConfig('codepay_key'), $request->input('sign'), false)) {
             if (PaymentHelper::paymentReceived($tradeNo)) {
                 exit('success');
             }

+ 6 - 3
app/Utils/Payments/Credit.php

@@ -11,9 +11,12 @@ use Illuminate\Http\Request;
 
 class Credit implements Gateway
 {
-    public static array $methodDetails = [
-        'key' => 'credit',
-    ];
+    public static function metadata(): array
+    {
+        return [
+            'key' => 'credit',
+        ];
+    }
 
     public function purchase(Request $request): JsonResponse
     {

+ 12 - 5
app/Utils/Payments/Cryptomus.php

@@ -14,11 +14,6 @@ class Cryptomus implements Gateway
 {
     private const API_URL = 'https://api.cryptomus.com/';
 
-    public static array $methodDetails = [
-        'key' => 'cryptomus',
-        'settings' => ['cryptomus_merchant_uuid', 'cryptomus_key'],
-    ];
-
     private string $apiKey;
 
     private string $mid;
@@ -29,6 +24,18 @@ class Cryptomus implements Gateway
         $this->apiKey = sysConfig('cryptomus_api_key');
     }
 
+    public static function metadata(): array
+    {
+        return [
+            'key' => 'cryptomus',
+            'method' => ['other'],
+            'settings' => [
+                'cryptomus_merchant_uuid' => null,
+                'cryptomus_api_key' => null,
+            ],
+        ];
+    }
+
     public function purchase(Request $request): JsonResponse
     {
         $payment = PaymentHelper::createPayment(auth()->id(), $request->input('id'), $request->input('amount'));

+ 19 - 4
app/Utils/Payments/EPay.php

@@ -11,10 +11,25 @@ use Log;
 
 class EPay implements Gateway
 {
-    public static array $methodDetails = [
-        'key' => 'epay',
-        'settings' => ['epay_url', 'epay_mch_id', 'epay_key'],
-    ];
+    public static function metadata(): array
+    {
+        if (auth()->user()?->can('admin.test.epay')) {
+            $button = '<div class="col-md-7"><button class="btn btn-primary" type="button" onclick="epayInfo()">'.trans('admin.query').'</button></div>';
+        }
+
+        return [
+            'key' => 'epay',
+            'method' => ['ali', 'qq', 'wechat'],
+            'settings' => [
+                'epay_url' => [
+                    'type' => 'url',
+                ],
+                'epay_mch_id' => null,
+                'epay_key' => null,
+            ],
+            'button' => $button ?? null,
+        ];
+    }
 
     public function purchase(Request $request): JsonResponse
     {

+ 13 - 5
app/Utils/Payments/F2Fpay.php

@@ -13,11 +13,6 @@ use Log;
 
 class F2FPay implements Gateway
 {
-    public static array $methodDetails = [
-        'key' => 'f2fpay',
-        'settings' => ['f2fpay_app_id', 'f2fpay_private_key', 'f2fpay_public_key'],
-    ];
-
     private static AlipayF2F $aliClient;
 
     public function __construct()
@@ -30,6 +25,19 @@ class F2FPay implements Gateway
         ]);
     }
 
+    public static function metadata(): array
+    {
+        return [
+            'key' => 'f2fpay',
+            'method' => ['ali'],
+            'settings' => [
+                'f2fpay_app_id' => null,
+                'f2fpay_private_key' => null,
+                'f2fpay_public_key' => null,
+            ],
+        ];
+    }
+
     public function purchase(Request $request): JsonResponse
     {
         $payment = PaymentHelper::createPayment(auth()->id(), $request->input('id'), $request->input('amount'));

+ 6 - 3
app/Utils/Payments/Manual.php

@@ -12,9 +12,12 @@ use Illuminate\Http\Request;
 
 class Manual implements Gateway
 {
-    public static array $methodDetails = [
-        'key' => 'manual',
-    ];
+    public static function metadata(): array
+    {
+        return [
+            'key' => 'manual',
+        ];
+    }
 
     public function purchase(Request $request): JsonResponse
     {

+ 12 - 5
app/Utils/Payments/PayBeaver.php

@@ -19,11 +19,6 @@ class PayBeaver implements Gateway
 {
     private const API_URL = 'https://api.paybeaver.com/api/v1/developer';
 
-    public static array $methodDetails = [
-        'key' => 'paybeaver',
-        'settings' => ['paybeaver_app_id', 'paybeaver_app_secret'],
-    ];
-
     private string $appId;
 
     private string $appSecret;
@@ -34,6 +29,18 @@ class PayBeaver implements Gateway
         $this->appSecret = sysConfig('paybeaver_app_secret');
     }
 
+    public static function metadata(): array
+    {
+        return [
+            'key' => 'paybeaver',
+            'method' => ['ali', 'wechat'],
+            'settings' => [
+                'paybeaver_app_id' => null,
+                'paybeaver_app_secret' => null,
+            ],
+        ];
+    }
+
     public function purchase(Request $request): JsonResponse
     {
         $payment = PaymentHelper::createPayment(auth()->id(), $request->input('id'), $request->input('amount'));

+ 13 - 6
app/Utils/Payments/PayJs.php

@@ -11,11 +11,6 @@ use Xhat\Payjs\Payjs as Pay;
 
 class PayJs implements Gateway
 {
-    public static array $methodDetails = [
-        'key' => 'payjs',
-        'settings' => ['payjs_mch_id', 'payjs_key'],
-    ];
-
     private static array $config;
 
     public function __construct()
@@ -26,6 +21,18 @@ class PayJs implements Gateway
         ];
     }
 
+    public static function metadata(): array
+    {
+        return [
+            'key' => 'payjs',
+            'method' => ['wechat'],
+            'settings' => [
+                'payjs_mch_id' => null,
+                'payjs_key' => null,
+            ],
+        ];
+    }
+
     public function purchase(Request $request): JsonResponse
     {
         $payment = PaymentHelper::createPayment(auth()->id(), $request->input('id'), $request->input('amount'));
@@ -40,7 +47,7 @@ class PayJs implements Gateway
         // 获取收款二维码内容
         $payment->update(['qr_code' => 1, 'url' => $result]);
 
-        //$this->addPamentCallback($payment->trade_no, null, $payment->amount * 100);
+        // $this->addPamentCallback($payment->trade_no, null, $payment->amount * 100);
         return response()->json(['status' => 'success', 'data' => $payment->trade_no, 'message' => trans('user.payment.order_creation.success')]);
     }
 

+ 13 - 5
app/Utils/Payments/PayPal.php

@@ -11,11 +11,6 @@ use Log;
 
 class PayPal implements Gateway
 {
-    public static array $methodDetails = [
-        'key' => 'paypal',
-        'settings' => ['paypal_client_id', 'paypal_client_secret', 'paypal_app_id'],
-    ];
-
     protected static \Srmklive\PayPal\Services\PayPal $provider;
 
     public function __construct()
@@ -41,6 +36,19 @@ class PayPal implements Gateway
         self::$provider->getAccessToken();
     }
 
+    public static function metadata(): array
+    {
+        return [
+            'key' => 'paypal',
+            'method' => ['other'],
+            'settings' => [
+                'paypal_client_id' => null,
+                'paypal_client_secret' => null,
+                'paypal_app_id' => null,
+            ],
+        ];
+    }
+
     public function purchase(Request $request): JsonResponse
     {
         $payment = PaymentHelper::createPayment(auth()->id(), $request->input('id'), $request->input('amount'));

+ 101 - 0
app/Utils/Payments/PaymentManager.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace App\Utils\Payments;
+
+use App\Utils\Library\Templates\Gateway;
+
+class PaymentManager
+{
+    public static function getSettingsFormData(): array
+    {
+        return cache()->rememberForever('payment_forms', function () {
+            $formData = [];
+            foreach (self::discover() as $key => $gateway) {
+                if (isset($gateway['metadata']['settings'])) {
+                    $formData[$key]['settings'] = $gateway['metadata']['settings'];
+
+                    if (isset($gateway['metadata']['button'])) {
+                        $formData[$key]['button'] = $gateway['metadata']['button'];
+                    }
+                }
+            }
+
+            return $formData;
+        });
+    }
+
+    public static function discover(): array
+    {
+        return cache()->rememberForever('discovered_payments', function () {
+            $gateways = [];
+            foreach (glob(app_path('Utils/Payments/*.php')) as $file) {
+                $className = 'App\\Utils\\Payments\\'.basename($file, '.php');
+                if (class_exists($className)) {
+                    $interfaces = class_implements($className);
+                    if ($interfaces && in_array(Gateway::class, $interfaces, true)) {
+                        $metadata = $className::metadata();
+                        $gateways[$metadata['key']] = [
+                            'class' => $className,
+                            'metadata' => $metadata,
+                        ];
+                    }
+                }
+            }
+
+            return $gateways;
+        });
+    }
+
+    public static function getAvailable(): array
+    {
+        return cache()->rememberForever('available_payments', function () {
+            foreach (self::discover() as $key => $gateway) {
+                if (isset($gateway['metadata']['settings'])) {
+                    $allConfigsExist = true;
+                    foreach ($gateway['metadata']['settings'] as $setting => $config) {
+                        if (! sysConfig($setting)) {
+                            $allConfigsExist = false;
+                            break;
+                        }
+                    }
+                    if ($allConfigsExist) {
+                        $available[] = $key;
+                    }
+                }
+            }
+
+            return $available ?? [];
+        });
+    }
+
+    public static function getLabels(bool $history = false): array
+    {
+        return cache()->rememberForever('payment_labels', function () use ($history) {
+            if ($history) {
+                $labels = [
+                    'bitpayx' => trans('admin.system.payment.channel.bitpayx'),
+                    'youzan' => trans('admin.system.payment.channel.youzan'),
+                ];
+            }
+
+            foreach (self::discover() as $key => $gateway) {
+                $labels[$key] = trans("admin.system.payment.channel.$key");
+            }
+
+            return $labels ?? [];
+        });
+    }
+
+    public static function getPaymentsByMethod(string $method): array
+    {
+        return cache()->rememberForever("{$method}_payments", function () use ($method) {
+            foreach (self::discover() as $key => $gateway) {
+                if (isset($gateway['metadata']['method']) && in_array($method, $gateway['metadata']['method'], true)) {
+                    $lists[trans("admin.system.payment.channel.$key")] = $key;
+                }
+            }
+
+            return $lists ?? [];
+        });
+    }
+}

+ 14 - 7
app/Utils/Payments/Stripe.php

@@ -17,16 +17,24 @@ use UnexpectedValueException;
 
 class Stripe implements Gateway
 {
-    public static array $methodDetails = [
-        'key' => 'stripe',
-        'settings' => ['stripe_public_key', 'stripe_secret_key'],
-    ];
-
     public function __construct()
     {
         \Stripe\Stripe::setApiKey(sysConfig('stripe_secret_key'));
     }
 
+    public static function metadata(): array
+    {
+        return [
+            'key' => 'stripe',
+            'method' => ['ali', 'wechat', 'other'],
+            'settings' => [
+                'stripe_public_key' => null,
+                'stripe_secret_key' => null,
+                'stripe_signing_secret' => null,
+            ],
+        ];
+    }
+
     public function purchase(Request $request): JsonResponse
     {
         $type = (int) $request->input('type');
@@ -136,8 +144,7 @@ class Stripe implements Gateway
         switch ($event->type) {
             case 'checkout.session.completed':
 
-                /* @var $session Session */
-                $session = $event->data->object;
+                /* @var $session Session */ $session = $event->data->object;
 
                 // Check if the order is paid (e.g., from a card payment)
                 //

+ 14 - 4
app/Utils/Payments/THeadPay.php

@@ -11,10 +11,20 @@ use Log;
 
 class THeadPay implements Gateway
 {
-    public static array $methodDetails = [
-        'key' => 'theadpay',
-        'settings' => ['theadpay_mchid', 'theadpay_key'],
-    ];
+    public static function metadata(): array
+    {
+        return [
+            'key' => 'theadpay',
+            'method' => ['ali'],
+            'settings' => [
+                'theadpay_url' => [
+                    'type' => 'url',
+                ],
+                'theadpay_mchid' => null,
+                'theadpay_key' => null,
+            ],
+        ];
+    }
 
     public function purchase(Request $request): JsonResponse
     {

+ 1 - 1
app/helpers.php

@@ -58,7 +58,7 @@ if (! function_exists('formatTime')) {
 
 // 获取系统设置
 if (! function_exists('sysConfig')) {
-    function sysConfig(?string $key = null, ?string $default = null): array|null|string
+    function sysConfig(?string $key = null, ?string $default = null): array|string|int|null
     {
         return $key ? config("settings.$key", $default) : config('settings');
     }

+ 0 - 15
config/common.php

@@ -2,21 +2,6 @@
 
 return [
     'payment' => [
-        'labels' => [
-            'bitpayx' => '麻瓜宝',
-            'codepay' => '码支付',
-            'credit' => '余额',
-            'epay' => '易支付',
-            'f2fpay' => '支付宝当面付',
-            'manual' => '人工支付',
-            'paybeaver' => '海狸支付',
-            'payjs' => 'PayJs',
-            'paypal' => 'PayPal',
-            'stripe' => 'Stripe',
-            'cryptomus' => 'Cryptomus',
-            'theadpay' => '平头哥支付',
-            'youzan' => '有赞云',
-        ],
         'icon' => [
             0 => 'creditpay.svg',
             1 => 'alipay.png',

+ 0 - 31
config/tasks.php

@@ -1,31 +0,0 @@
-<?php
-
-return [
-    'chunk' => env('TASKS_CHUNK', 3000), // 大数据量修改,分段处理,减少内存使用
-    'clean' => [
-        'notification_logs' => env('TASKS_NOTIFICATION_LOGS', '-18 months'), // 清理通知日志
-        'node_daily_logs' => env('TASKS_NODE_DAILY_LOGS', '-13 months'), // 清除节点每天流量数据日志
-        'node_hourly_logs' => env('TASKS_NODE_HOURLY_LOGS', '-2 weeks'), // 清除节点每小时流量数据日志
-        'node_heartbeats' => env('TASKS_NODE_HEARTBEATS', '-30 minutes'), // 清除节点负载信息日志
-        'node_online_logs' => env('TASKS_NODE_ONLINE_LOGS', '-2 weeks'), // 清除节点在线用户数日志
-        'payments' => env('TASKS_PAYMENTS', '-1 year'), // 清理在线支付日志
-        'rule_logs' => env('TASKS_RULE_lOGS', '-3 months'), // 清理审计触发日志
-        'node_online_ips' => env('TASKS_NODE_ONLINE_IPS', '-1 week'), // 清除用户连接IP
-        'user_baned_logs' => env('TASKS_USER_BANED_LOGS', '-3 months'), // 清除用户封禁日志
-        'user_daily_logs_nodes' => env('TASKS_USER_DAILY_LOGS_NODES', '-36 days'), // 清除用户各节点的每天流量数据日志
-        'user_daily_logs_total' => env('TASKS_USER_DAILY_LOGS_TOTAL', '-3 months'), // 清除用户节点总计的每天流量数据日志
-        'user_hourly_logs' => env('TASKS_USER_HOURLY_LOGS', '-3 days'), // 清除用户每时各流量数据日志 最少值为 2
-        'login_logs' => env('TASKS_LOGIN_LOGS', '-3 months'), // 清除用户登陆日志
-        'subscribe_logs' => env('TASKS_SUBSCRIBE_LOGS', '-2 months'), // 清理用户订阅请求日志
-        'traffic_logs' => env('TASKS_TRAFFIC_LOGS', '-3 days'), // 清除用户流量日志
-        'unpaid_orders' => env('UNPAID_ORDERS', '-1 year'), // 清除用户流量日志
-    ],
-    'close' => [
-        'tickets' => env('TASKS_TICKETS', 72), // 自动关闭工单,单位:小时
-        'confirmation_orders' => env('TASKS_CONFIRMATION_ORDERS', 12), // 自动关闭人工支付订单,单位:小时
-        'orders' => env('TASKS_ORDERS', 15), // 自动关闭订单,单位:分钟
-        'verify' => env('TASKS_VERIFY', 15), // 自动失效验证码,单位:分钟
-    ],
-    'release_port' => env('TASKS_RELEASE_PORT', 30), // 端口自动释放,单位:天
-    'recently_heartbeat' => env('TASKS_RECENTLY_HEARTBEAT', '-10 minutes'), // 节点近期负载
-];

+ 82 - 0
database/migrations/2025_04_26_232606_modified_system.php

@@ -0,0 +1,82 @@
+<?php
+
+use App\Models\Config;
+use App\Models\Order;
+use Illuminate\Database\Migrations\Migration;
+
+return new class extends Migration
+{
+    private static array $dropConfigs = ['bitpay_secret', 'is_push_bear', 'is_subscribe_ban', 'is_traffic_ban', 'is_checkin'];
+
+    private static array $renameConfigs = [
+        'aff_salt' => 'affiliate_link_salt',
+        'max_rand_traffic' => 'checkin_reward_max',
+        'min_rand_traffic' => 'checkin_reward',
+        'referral_type' => 'referral_reward_type',
+        'subscribe_ban_times' => 'subscribe_rate_limit',
+        'traffic_ban_time' => 'ban_duration',
+        'traffic_ban_value' => 'traffic_abuse_limit',
+        'traffic_limit_time' => 'checkin_interval',
+        'website_analytics' => 'website_statistics_code',
+        'website_callback_url' => 'payment_callback_url',
+        'website_customer_service' => 'website_customer_service_code',
+    ];
+
+    public function up(): void
+    {
+        if (Config::exists()) {
+            Config::create([['name' => 'tasks_chunk', 'value' => 3000], ['name' => 'tasks_clean', 'value' => '{"notification_logs":"-18 months","node_daily_logs":"-13 months","node_hourly_logs":"-14 days","node_heartbeats":"-30 minutes","node_online_logs":"-14 days","payments":"-1 years","rule_logs":"-3 months","node_online_ips":"-7 days","user_baned_logs":"-3 months","user_daily_logs_nodes":"-36 days","user_daily_logs_total":"-3 months","user_hourly_logs":"-3 days","login_logs":"-3 months","subscribe_logs":"-2 months","traffic_logs":"-3 days","unpaid_orders":"-1 years"}'], ['name' => 'tasks_close', 'value' => '{"tickets":"-72 hours","confirmation_orders":"-12 hours","orders":"-15 minutes","verify":"-15 minutes"}'], ['name' => 'recently_heartbeat', 'value' => '-10 minutes']]);
+
+            if (! sysConfig('is_subscribe_ban')) {
+                Config::whereName('subscribe_ban_times')->update(['value' => null]);
+            }
+
+            if (! sysConfig('is_traffic_ban')) {
+                Config::whereName('traffic_ban_time')->update(['value' => null]);
+            }
+
+            if (! sysConfig('is_checkin')) {
+                Config::whereName('traffic_limit_time')->update(['value' => null]);
+            }
+
+            foreach (self::$renameConfigs as $old => $new) {
+                Config::whereName($old)->update(['name' => $new]);
+            }
+
+            Config::whereName('auto_release_port')->update(['value' => sysConfig('auto_release_port') ? 30 : null]);
+
+            foreach (self::$dropConfigs as $config) {
+                Config::destroy(['name' => $config]);
+            }
+        }
+
+        Order::wherePayWay('balance')->update(['pay_way' => 'credit']);
+    }
+
+    public function down(): void
+    {
+        Config::destroy([['name' => 'tasks_chunk'], ['name' => 'tasks_clean'], ['name' => 'tasks_close'], ['name' => 'recently_heartbeat']]);
+
+        foreach (self::$dropConfigs as $config) {
+            Config::insert(['name' => $config]);
+        }
+
+        foreach (self::$renameConfigs as $old => $new) {
+            Config::whereName($new)->update(['name' => $old]);
+        }
+
+        if (sysConfig('subscribe_ban_times')) {
+            Config::whereName('is_subscribe_ban')->update(['value' => 1]);
+        }
+
+        if (sysConfig('traffic_ban_time')) {
+            Config::whereName('is_traffic_ban')->update(['value' => 1]);
+        }
+
+        if (sysConfig('traffic_limit_time')) {
+            Config::whereName('is_checkin')->update(['value' => 1]);
+        }
+
+        Config::whereName('auto_release_port')->update(['value' => sysConfig('auto_release_port') ? 1 : null]);
+    }
+};

+ 26 - 26
database/seeders/ConfigSeeder.php

@@ -11,13 +11,12 @@ class ConfigSeeder extends Seeder
         'account_expire_notification',
         'active_times',
         'admin_invite_days',
-        'aff_salt',
+        'affiliate_link_salt',
         'alipay_qrcode',
         'AppStore_id',
         'AppStore_password',
         'auto_release_port',
         'bark_key',
-        'bitpay_secret',
         'captcha_key',
         'captcha_secret',
         'codepay_id',
@@ -48,7 +47,6 @@ class ConfigSeeder extends Seeder
         'is_AliPay',
         'is_ban_status',
         'is_captcha',
-        'is_checkin',
         'is_clear_log',
         'is_custom_subscribe',
         'is_email_filtering',
@@ -56,21 +54,18 @@ class ConfigSeeder extends Seeder
         'is_free_code',
         'is_invite_register',
         'is_otherPay',
-        'is_push_bear',
         'is_QQPay',
         'is_rand_port',
         'is_register',
-        'is_subscribe_ban',
-        'is_traffic_ban',
         'is_WeChatPay',
         'iYuu_token',
         'maintenance_content',
         'maintenance_mode',
         'maintenance_time',
         'max_port',
-        'max_rand_traffic',
+        'checkin_reward_max',
         'min_port',
-        'min_rand_traffic',
+        'checkin_reward',
         'node_blocked_notification',
         'node_daily_notification',
         'node_offline_notification',
@@ -95,7 +90,7 @@ class ConfigSeeder extends Seeder
         'referral_percent',
         'referral_status',
         'referral_traffic',
-        'referral_type',
+        'referral_reward_type',
         'register_ip_limit',
         'reset_password_times',
         'reset_traffic',
@@ -105,7 +100,7 @@ class ConfigSeeder extends Seeder
         'stripe_secret_key',
         'stripe_signing_secret',
         'subject_name',
-        'subscribe_ban_times',
+        'subscribe_rate_limit',
         'subscribe_domain',
         'subscribe_max',
         'telegram_token',
@@ -116,9 +111,9 @@ class ConfigSeeder extends Seeder
         'ticket_closed_notification',
         'ticket_created_notification',
         'ticket_replied_notification',
-        'traffic_ban_time',
-        'traffic_ban_value',
-        'traffic_limit_time',
+        'ban_duration',
+        'traffic_abuse_limit',
+        'checkin_interval',
         'traffic_warning_percent',
         'trojan_license',
         'username_type',
@@ -126,9 +121,9 @@ class ConfigSeeder extends Seeder
         'v2ray_license',
         'v2ray_tls_provider',
         'webmaster_email',
-        'website_analytics',
-        'website_callback_url',
-        'website_customer_service',
+        'website_statistics_code',
+        'payment_callback_url',
+        'website_customer_service_code',
         'website_home_logo',
         'website_logo',
         'website_name',
@@ -141,6 +136,10 @@ class ConfigSeeder extends Seeder
         'wechat_qrcode',
         'wechat_secret',
         'wechat_token',
+        'tasks_chunk',
+        'tasks_clean',
+        'tasks_close',
+        'recently_heartbeat',
     ];
 
     public function run(): void
@@ -157,10 +156,9 @@ class ConfigSeeder extends Seeder
             'reset_password_times' => 3,
             'website_url' => config('app.url'),
             'active_times' => 3,
-            'is_checkin' => 1,
-            'min_rand_traffic' => 10,
-            'max_rand_traffic' => 500,
-            'traffic_limit_time' => 1440,
+            'checkin_reward' => 10,
+            'checkin_reward_max' => 500,
+            'checkin_interval' => 1440,
             'referral_traffic' => 1024,
             'referral_percent' => 0.2,
             'referral_money' => 100,
@@ -173,19 +171,21 @@ class ConfigSeeder extends Seeder
             'subscribe_max' => 3,
             'min_port' => 10000,
             'max_port' => 65535,
-            'is_traffic_ban' => 1,
-            'traffic_ban_value' => 10,
-            'traffic_ban_time' => 60,
+            'traffic_abuse_limit' => 10,
+            'ban_duration' => 60,
             'is_clear_log' => 1,
-            'is_subscribe_ban' => 1,
-            'subscribe_ban_times' => 20,
-            'auto_release_port' => 1,
+            'subscribe_rate_limit' => 20,
+            'auto_release_port' => 30,
             'register_ip_limit' => 5,
             'detection_check_times' => 3,
             'user_invite_days' => 7,
             'admin_invite_days' => 7,
             'standard_currency' => 'CNY',
             'redirect_url' => 'https://www.baidu.com',
+            'tasks_chunk' => 3000,
+            'tasks_clean' => '{"notification_logs":"-18 months","node_daily_logs":"-13 months","node_hourly_logs":"-14 days","node_heartbeats":"-30 minutes","node_online_logs":"-14 days","payments":"-1 years","rule_logs":"-3 months","node_online_ips":"-7 days","user_baned_logs":"-3 months","user_daily_logs_nodes":"-36 days","user_daily_logs_total":"-3 months","user_hourly_logs":"-3 days","login_logs":"-3 months","subscribe_logs":"-2 months","traffic_logs":"-3 days","unpaid_orders":"-1 years"}',
+            'tasks_close' => '{"tickets":"-72 hours","confirmation_orders":"-12 hours","orders":"-15 minutes","verify":"-15 minutes"}',
+            'recently_heartbeat' => '-10 minutes',
         ];
 
         foreach ($presetDates as $key => $value) {

ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/css/all.min.css


ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/css/brands.min.css


ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/css/fontawesome.min.css


+ 1 - 1
public/assets/global/fonts/font-awesome/css/regular.min.css

@@ -1,5 +1,5 @@
 /*!
- * Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
+ * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
  * Copyright 2024 Fonticons, Inc.
  */

+ 1 - 1
public/assets/global/fonts/font-awesome/css/solid.min.css

@@ -1,5 +1,5 @@
 /*!
- * Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
+ * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
  * Copyright 2024 Fonticons, Inc.
  */

ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/css/svg-with-js.min.css


+ 1 - 1
public/assets/global/fonts/font-awesome/css/v4-font-face.min.css

@@ -1,5 +1,5 @@
 /*!
- * Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
+ * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
  * Copyright 2024 Fonticons, Inc.
  */

ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/css/v4-shims.min.css


+ 1 - 1
public/assets/global/fonts/font-awesome/css/v5-font-face.min.css

@@ -1,5 +1,5 @@
 /*!
- * Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
+ * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
  * Copyright 2024 Fonticons, Inc.
  */

ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/js/all.min.js


ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/js/brands.min.js


ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/js/conflict-detection.min.js


ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/js/fontawesome.min.js


ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/js/regular.min.js


ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/js/solid.min.js


ファイルの差分が大きいため隠しています
+ 1 - 1
public/assets/global/fonts/font-awesome/js/v4-shims.min.js


BIN
public/assets/global/fonts/font-awesome/webfonts/fa-brands-400.ttf


BIN
public/assets/global/fonts/font-awesome/webfonts/fa-brands-400.woff2


BIN
public/assets/global/fonts/font-awesome/webfonts/fa-regular-400.ttf


BIN
public/assets/global/fonts/font-awesome/webfonts/fa-regular-400.woff2


BIN
public/assets/global/fonts/font-awesome/webfonts/fa-solid-900.ttf


BIN
public/assets/global/fonts/font-awesome/webfonts/fa-solid-900.woff2


BIN
public/assets/global/fonts/font-awesome/webfonts/fa-v4compatibility.ttf


BIN
public/assets/global/fonts/font-awesome/webfonts/fa-v4compatibility.woff2


+ 90 - 0
public/assets/global/js/Plugin/toastr.js

@@ -0,0 +1,90 @@
+(function (global, factory) {
+  if (typeof define === "function" && define.amd) {
+    define("/Plugin/toastr", ["exports", "Plugin"], factory);
+  } else if (typeof exports !== "undefined") {
+    factory(exports, require("Plugin"));
+  } else {
+    var mod = {
+      exports: {}
+    };
+    factory(mod.exports, global.Plugin);
+    global.PluginToastr = mod.exports;
+  }
+})(this, function (_exports, _Plugin2) {
+  "use strict";
+
+  Object.defineProperty(_exports, "__esModule", {
+    value: true
+  });
+  _exports.default = void 0;
+  _Plugin2 = babelHelpers.interopRequireDefault(_Plugin2);
+  var NAME = 'toastr';
+
+  var Toastr =
+  /*#__PURE__*/
+  function (_Plugin) {
+    babelHelpers.inherits(Toastr, _Plugin);
+
+    function Toastr() {
+      babelHelpers.classCallCheck(this, Toastr);
+      return babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(Toastr).apply(this, arguments));
+    }
+
+    babelHelpers.createClass(Toastr, [{
+      key: "getName",
+      value: function getName() {
+        return NAME;
+      }
+    }, {
+      key: "render",
+      value: function render() {
+        this.$el.data('toastrWrapApi', this);
+      }
+    }, {
+      key: "show",
+      value: function show(e) {
+        if (typeof toastr === 'undefined') {
+          return;
+        }
+
+        e.preventDefault();
+        var options = this.options;
+        var message = options.message || '';
+        var type = options.type || 'info';
+        var title = options.title || undefined;
+
+        switch (type) {
+          case 'success':
+            toastr.success(message, title, options);
+            break;
+
+          case 'warning':
+            toastr.warning(message, title, options);
+            break;
+
+          case 'error':
+            toastr.error(message, title, options);
+            break;
+
+          case 'info':
+            toastr.info(message, title, options);
+            break;
+
+          default:
+            toastr.info(message, title, options);
+        }
+      }
+    }], [{
+      key: "api",
+      value: function api() {
+        return 'click|show';
+      }
+    }]);
+    return Toastr;
+  }(_Plugin2.default);
+
+  _Plugin2.default.register(NAME, Toastr);
+
+  var _default = Toastr;
+  _exports.default = _default;
+});

ファイルの差分が大きいため隠しています
+ 0 - 0
public/assets/global/vendor/toastr/toastr.min.css


ファイルの差分が大きいため隠しています
+ 0 - 0
public/assets/global/vendor/toastr/toastr.min.js


+ 59 - 35
resources/lang/zh_CN/admin.php

@@ -428,18 +428,16 @@ return [
         ],
         'no_permission' => '您没有权限修改参数!',
         'system' => [
+            'web' => '网站常规',
             'account' => '账号设置',
+            'node' => '节点设置',
+            'security' => '安全&验证',
+            'payment' => '支付系统',
+            'notify' => '通知系统',
             'auto_job' => '自动任务',
             'check_in' => '签到系统',
-            'extend' => '拓展功能',
             'menu' => '菜单',
-            'node' => '节点设置',
-            'notify' => '通知系统',
-            'other' => 'LOGO|客服|统计',
-            'payment' => '支付系统',
-            'promotion' => '推广系统',
             'title' => '系统配置',
-            'web' => '网站常规',
         ],
     ],
     'sort_asc' => '排序值越大排越前',
@@ -454,7 +452,7 @@ return [
         ],
         'active_times' => '激活账号次数',
         'admin_invite_days' => '管理员-邀请码有效期',
-        'aff_salt' => '邀请链接 用户信息字符化',
+        'affiliate_link_salt' => '邀请链接 用户信息字符化',
         'alipay_qrcode' => '支付宝二维码',
         'auto_release_port' => '端口回收机制',
         'bark_key' => 'Bark设备号',
@@ -470,6 +468,34 @@ return [
         'codepay_id' => '码支付ID',
         'codepay_key' => '通信密钥',
         'codepay_url' => '请求URL',
+        'tasks_chunk' => '分段处理量',
+        'recently_heartbeat' => '节点负载近期阈值',
+        'tasks' => [
+            'clean' => [
+                'notification_logs' => '通知日志',
+                'node_daily_logs' => '节点每天流量数据',
+                'node_hourly_logs' => '节点 流量数据',
+                'node_heartbeats' => '节点负载/心跳数据',
+                'node_online_logs' => '节点在线用户数据',
+                'payments' => '支付数据',
+                'rule_logs' => '审计触发数据',
+                'node_online_ips' => '用户连接IP数据',
+                'user_baned_logs' => '用户封禁日志',
+                'user_daily_logs_nodes' => '用户每日各节点消耗流量数据',
+                'user_daily_logs_total' => '用户每日消耗流量数据',
+                'user_hourly_logs' => '用户每时各节点消耗流量数据',
+                'login_logs' => '用户登录数据',
+                'subscribe_logs' => '订阅请求数据',
+                'traffic_logs' => '流量消耗细节数据',
+                'unpaid_orders' => '未支付订单',
+            ],
+            'close' => [
+                'tickets' => '工单',
+                'confirmation_orders' => '人工支付订单',
+                'orders' => '支付订单',
+                'verify' => '邮箱验证码时效',
+            ],
+        ],
         'data_anomaly_notification' => '流量异常通知',
         'data_exhaust_notification' => '流量耗尽通知',
         'ddns_key' => 'DNS服务商Key',
@@ -500,8 +526,8 @@ return [
             'account_expire_notification' => '通知用户账号即将到期',
             'active_times' => '24小时内可以通过邮件激活账号次数',
             'admin_invite_days' => '管理员生成邀请码的有效期',
-            'aff_salt' => '留空时,邀请链接将显示用户ID;填入任意英文/数字 即可对用户链接ID进行加密',
-            'auto_release_port' => '被封禁/过期 <code>'.config('tasks.release_port').'</code> 天的账号端口自动释放',
+            'affiliate_link_salt' => '留空时,邀请链接将显示用户ID;填入任意英文/数字 即可对用户链接ID进行加密',
+            'auto_release_port' => '被封禁/过期的账号端口将在N天自动释放',
             'bark_key' => '推送消息到iOS设备,需要在iOS设备里装一个名为Bark的应用,取网址后的一长串字符串,启用Bark,请务必填入本值',
             'captcha_key' => '浏览<a href="https://proxypanel.gitbook.io/wiki/captcha" target="_blank">设置指南</a>来设置',
             'data_anomaly_notification' => '1小时内流量超过异常阈值将通知管理员',
@@ -523,7 +549,6 @@ return [
             'is_activate_account' => '启用后用户需要通过邮件来激活账号',
             'is_ban_status' => '(慎重)封禁整个账号会重置账号的所有数据且会导致用户无法登录,不开启状态下只封禁用户代理',
             'is_captcha' => '启用后 登录/注册 需要进行验证码认证',
-            'is_checkin' => '登录时将根据流量范围随机得到流量',
             'is_clear_log' => '(推荐)启用后自动清除无用日志',
             'is_custom_subscribe' => '启用后,订阅信息顶部将显示过期时间、剩余流量(只支持个别客户端)',
             'is_email_filtering' => '黑名单:用户可使用任意黑名单外的邮箱注册;白名单:用户只能选择使用白名单中的邮箱后缀注册',
@@ -531,12 +556,11 @@ return [
             'is_free_code' => '关闭后免费邀请码不可见',
             'is_rand_port' => '注册、添加用户时随机生成端口',
             'is_register' => '关闭后无法注册',
-            'is_subscribe_ban' => '启用后用户订阅链接请求超过设定阈值则自动封禁',
-            'is_traffic_ban' => '1小时内流量超过异常阈值则自动封号(仅禁用代理)',
             'maintenance_content' => '自定义维护内容信息',
             'maintenance_mode' => '启用后,用户访问转移至维护界面 | 管理员使用 <a href="javascript:(0)">:url</a> 登录',
             'maintenance_time' => '用于维护界面倒计时',
             'min_port' => '端口范围:1000~65535',
+            'checkin_reward' => '每次签到可获得的随机流量范围',
             'node_blocked_notification' => '每小时检测节点是否被阻断并通知管理员',
             'node_daily_notification' => '报告各节点前一天的流量消耗情况',
             'node_offline_notification' => '每10分钟检测节点离线情况并通知管理员',
@@ -557,14 +581,14 @@ return [
             'referral_percent' => '根据推广链接注册的账号每笔消费推广人可以分成的比例',
             'referral_status' => '关闭后用户不可见,但是不影响其正常邀请返利',
             'referral_traffic' => '根据推广链接、邀请码注册则赠送相应的流量',
-            'referral_type' => '切换模式后旧数据不变,新的返利按新的模式计算',
+            'referral_reward_type' => '切换模式后旧数据不变,新的返利按新的模式计算',
             'register_ip_limit' => '24小时内同IP允许注册数量,为0/留空不限制',
             'reset_password_times' => '24小时内可以通过邮件重置密码次数',
             'reset_traffic' => '用户会按其购买套餐的日期自动重置可用流量',
             'server_chan_key' => '启用ServerChan,请务必填入本值(<a href="https://sct.ftqq.com/r/2626" target="_blank">申请 SCKEY</a>)',
             'standard_currency' => '网站中涉及金钱部分的默认货币',
             'subject_name' => '用于在支付渠道的商品标题显示',
-            'subscribe_ban_times' => '24小时内订阅链接请求次数限制',
+            'subscribe_rate_limit' => '24 小时内可请求订阅链接的最大次数,设为 0 则不启用此功能',
             'subscribe_domain' => '(推荐)防止面板域名被DNS投毒后无法正常订阅,需带http://或https://"',
             'subscribe_max' => '客户端订阅时取得几个节点,为0/留空时返回全部节点',
             'telegram_token' => '找 <a href=https://t.me/BotFather target=_blank>@BotFather</a> 申请机器人获取TOKEN',
@@ -572,18 +596,18 @@ return [
             'ticket_closed_notification' => '工单关闭通知用户',
             'ticket_created_notification' => '新工单通知管理/用户,取决于谁创建了新工单',
             'ticket_replied_notification' => '工单回复通知对方',
-            'traffic_ban_time' => '任何异常导致用户/订阅被封禁的时长,到期后自动解封',
-            'traffic_ban_value' => '1小时内超过该值,则触发自动封号',
-            'traffic_limit_time' => '间隔多久才可以再次签到',
+            'ban_duration' => '任何异常导致用户/订阅被封禁后的持续时间,到期后自动解封',
+            'traffic_abuse_limit' => '1 小时内流量超过此值将自动封号(仅禁用代理),设为 0 则不启用此功能',
+            'checkin_interval' => '用户每隔多久可签到一次,单位为分钟,设为 0 表示关闭签到功能',
             'traffic_warning_percent' => '【流量耗尽通知】开始阈值,每日通知用户',
             'user_invite_days' => '用户自行生成邀请的有效期',
             'username_type' => '规范站点用户账号的类型,默认为电子邮箱',
             'v2ray_tls_provider' => '后端自动签发/载入TLS证书时用(节点的设置值优先级高于此处)',
             'web_api_url' => '例:'.config('app.url'),
             'webmaster_email' => '错误提示时会提供管理员邮箱作为联系方式',
-            'website_analytics' => '统计JS',
-            'website_callback_url' => '防止因为网站域名被DNS投毒后导致支付无法正常回调,需带http://或https://',
-            'website_customer_service' => '客服JS',
+            'website_statistics_code' => '统计JS',
+            'payment_callback_url' => '防止因为网站域名被DNS投毒后导致支付无法正常回调,需带http://或https://',
+            'website_customer_service_code' => '客服JS',
             'website_name' => '发邮件时展示',
             'website_security_code' => '非空时必须通过<a href=":url" target="_blank">安全入口</a>加上安全码才可访问',
             'website_url' => '生成重置密码、在线支付必备',
@@ -601,7 +625,6 @@ return [
         'is_activate_account' => '激活账号',
         'is_ban_status' => '过期自动封禁',
         'is_captcha' => '验证码模式',
-        'is_checkin' => '签到加流量',
         'is_clear_log' => '自动清除日志',
         'is_custom_subscribe' => '高级订阅',
         'is_email_filtering' => '邮箱过滤机制',
@@ -611,13 +634,11 @@ return [
         'is_otherPay' => '特殊支付',
         'is_rand_port' => '随机端口',
         'is_register' => '用户注册',
-        'is_subscribe_ban' => '异常订阅请求自动封禁',
-        'is_traffic_ban' => '异常流量使用自动封号',
         'maintenance_content' => '维护介绍内容',
         'maintenance_mode' => '维护模式',
         'maintenance_time' => '维护结束时间',
         'min_port' => '端口范围',
-        'min_rand_traffic' => '流量范围',
+        'checkin_reward' => '签到奖励范围',
         'node_blocked_notification' => '节点阻断通知',
         'node_daily_notification' => '节点使用报告',
         'node_offline_notification' => '节点离线通知',
@@ -655,8 +676,9 @@ return [
         'payment' => [
             'attribute' => '支付设置',
             'channel' => [
-                'alipay' => '支付宝F2F',
+                'f2fpay' => '支付宝F2F',
                 'codepay' => '码支付',
+                'credit' => '余额',
                 'epay' => '易支付',
                 'manual' => '人工支付',
                 'paybeaver' => '海狸支付',
@@ -665,9 +687,11 @@ return [
                 'stripe' => 'Stripe',
                 'theadpay' => '平头哥支付',
                 'cryptomus' => 'Cryptomus',
+                'youzan' => '有赞云',
+                'bitpayx' => '麻瓜宝',
             ],
             'hint' => [
-                'alipay' => '本功能需要从<a href="https://open.alipay.com/platform/appManage.htm?#/create/" target="_blank">蚂蚁金服开放平台</a>申请权限及应用',
+                'f2fpay' => '本功能需要从<a href="https://open.alipay.com/platform/appManage.htm?#/create/" target="_blank">蚂蚁金服开放平台</a>申请权限及应用',
                 'codepay' => '请到 <a href="https://codepay.fateqq.com/i/377289" target="_blank">码支付</a>申请账号,然后下载登录其挂机软件',
                 'manual' => '设置后会自动开启对应显示',
                 'paybeaver' => '请到<a href="https://merchant.paybeaver.com/?aff_code=iK4GNuX8" target="_blank">海狸支付 PayBeaver</a>申请账号',
@@ -710,7 +734,7 @@ return [
         'referral_percent' => '返利比例',
         'referral_status' => '推广功能',
         'referral_traffic' => '注册送流量',
-        'referral_type' => '返利模式',
+        'referral_reward_type' => '返利模式',
         'register_ip_limit' => '同IP注册限制',
         'reset_password_times' => '重置密码次数',
         'reset_traffic' => '流量自动重置',
@@ -722,7 +746,7 @@ return [
         'cryptomus_merchant_uuid' => 'Merchant ID',
         'cryptomus_api_key' => 'API key',
         'subject_name' => '自定义商品名称',
-        'subscribe_ban_times' => '订阅请求阈值',
+        'subscribe_rate_limit' => '订阅请求限制',
         'subscribe_domain' => '节点订阅地址',
         'subscribe_max' => '订阅节点数',
         'telegram_token' => 'Telegram Token',
@@ -733,9 +757,9 @@ return [
         'ticket_closed_notification' => '工单关闭通知',
         'ticket_created_notification' => '新工单通知',
         'ticket_replied_notification' => '工单回复通知',
-        'traffic_ban_time' => '封号时长',
-        'traffic_ban_value' => '流量异常阈值',
-        'traffic_limit_time' => '时间间隔',
+        'ban_duration' => '封禁时长',
+        'traffic_abuse_limit' => '流量异常阈值',
+        'checkin_interval' => '签到间隔',
         'traffic_warning_percent' => '流量警告阈值',
         'trojan_license' => 'Trojan授权',
         'user_invite_days' => '用户-邀请码有效期',
@@ -749,9 +773,9 @@ return [
         'v2ray_tls_provider' => 'V2Ray TLS配置',
         'web_api_url' => '授权/后端访问域名',
         'webmaster_email' => '管理员邮箱',
-        'website_analytics' => '网站统计代码',
-        'website_callback_url' => '通用支付回调地址',
-        'website_customer_service' => '网站客服代码',
+        'website_statistics_code' => '网站统计代码',
+        'payment_callback_url' => '通用支付回调地址',
+        'website_customer_service_code' => '网站客服代码',
         'website_home_logo' => '首页LOGO',
         'website_logo' => '站内LOGO',
         'website_name' => '网站名称',

ファイルの差分が大きいため隠しています
+ 322 - 482
resources/views/admin/config/system.blade.php


+ 11 - 11
resources/views/admin/logs/order.blade.php

@@ -52,7 +52,7 @@
                     <div class="form-group col-lg-2 col-sm-6">
                         <select class="form-control show-tick" id="pay_way" name="pay_way" data-plugin="selectpicker" data-style="btn-outline byn-primary"
                                 title="{{ trans('model.order.pay_way') }}">
-                            @foreach (config('common.payment.labels') as $key => $value)
+                            @foreach ($paymentLabels as $key => $value)
                                 <option value="{{ $key }}">{{ $key . ' - ' . $value }}</option>
                             @endforeach
                         </select>
@@ -174,15 +174,15 @@
     <script src="/assets/global/js/Plugin/bootstrap-datepicker.js"></script>
     <script>
         $(document).ready(function() {
-            $('#is_coupon').selectpicker('val', @json(Request::query('is_coupon')));
-            $('#is_expire').selectpicker('val', @json(Request::query('is_expire')));
-            $('#pay_way').selectpicker('val', @json(Request::query('pay_way')));
-            $('#status').selectpicker('val', @json(Request::query('status')));
+            $("#is_coupon").selectpicker("val", @json(Request::query('is_coupon')));
+            $("#is_expire").selectpicker("val", @json(Request::query('is_expire')));
+            $("#pay_way").selectpicker("val", @json(Request::query('pay_way')));
+            $("#status").selectpicker("val", @json(Request::query('status')));
         });
 
         // 有效期
-        $('.input-daterange').datepicker({
-            format: 'yyyy-mm-dd'
+        $(".input-daterange").datepicker({
+            format: "yyyy-mm-dd"
         });
 
         @can('admin.order.edit')
@@ -193,17 +193,17 @@
                     oid: id,
                     status: status
                 }, function(ret) {
-                    if (ret.status === 'success') {
+                    if (ret.status === "success") {
                         swal.fire({
                             title: ret.message,
-                            icon: 'success',
+                            icon: "success",
                             timer: 1000,
-                            showConfirmButton: false,
+                            showConfirmButton: false
                         }).then(() => window.location.reload());
                     } else {
                         swal.fire({
                             title: ret.message,
-                            icon: 'error'
+                            icon: "error"
                         }).then(() => window.location.reload());
                     }
                 });

+ 107 - 106
resources/views/admin/node/info.blade.php

@@ -498,24 +498,25 @@
     <script>
         const string = "{{ strtolower(Str::random()) }}";
 
-        $('[name="next_renewal_date"]').datepicker({
-            format: 'yyyy-mm-dd'
+        $("[name='next_renewal_date']").datepicker({
+            format: "yyyy-mm-dd"
         });
 
         function calculateNextNextRenewalDate() {
-            const nextRenewalDate = $('#next_renewal_date').val();
-            const termValue = parseInt($('#subscription_term_value').val() || 0);
-            const termUnit = $('#subscription_term_unit').val();
+            const nextRenewalDate = $("#next_renewal_date").val();
+            const termValue = parseInt($("#subscription_term_value").val() || 0);
+            const termUnit = $("#subscription_term_unit").val();
+            const nextNextRenewalDate = $("#next_next_renewal_date");
 
             if (!nextRenewalDate || termValue <= 0) {
-                $('#next_next_renewal_date').val('');
+                nextNextRenewalDate.val("");
                 return;
             }
 
             const currentDate = new Date(nextRenewalDate);
             const originalDay = currentDate.getDate();
 
-            if (termUnit === 'months') {
+            if (termUnit === "months") {
                 // 获取当前月份和年份
                 let targetMonth = currentDate.getMonth() + termValue;
                 let targetYear = currentDate.getFullYear() + Math.floor(targetMonth / 12);
@@ -533,15 +534,15 @@
             } else {
                 // 处理天数和年份的情况
                 const adjustments = {
-                    days: 'Date',
-                    years: 'FullYear'
+                    days: "Date",
+                    years: "FullYear"
                 };
                 currentDate[`set${adjustments[termUnit]}`](
                     currentDate[`get${adjustments[termUnit]}`]() + termValue
                 );
             }
 
-            $('#next_next_renewal_date').val(currentDate.toISOString().split('T')[0]);
+            nextNextRenewalDate.val(currentDate.toISOString().split("T")[0]);
         }
 
         $(document).ready(function() {
@@ -557,17 +558,17 @@
         });
 
         function initializeUI() {
-            $('.single-setting').hide();
-            $('#v2_path').val('/' + string);
+            $(".single-setting").hide();
+            $("#v2_path").val("/" + string);
         }
 
         function bindEvents() {
-            $('input:radio[name="type"]').on('change', updateServiceType);
-            $('#obfs').on('changed.bs.select', toggleObfsParam);
-            $('#relay_node_id').on('changed.bs.select', toggleRelayConfig);
-            $('#v2_net').on('changed.bs.select', updateV2RaySettings);
-            $('#nodeForm').on('submit', formSubmit);
-            $(document).on('change', '#next_renewal_date, #subscription_term_value, #subscription_term_unit', calculateNextNextRenewalDate);
+            $("input:radio[name='type']").on("change", updateServiceType);
+            $("#obfs").on("changed.bs.select", toggleObfsParam);
+            $("#relay_node_id").on("changed.bs.select", toggleRelayConfig);
+            $("#v2_net").on("changed.bs.select", updateV2RaySettings);
+            $("#nodeForm").on("submit", formSubmit);
+            $(document).on("change", "#next_renewal_date, #subscription_term_value, #subscription_term_unit", calculateNextNextRenewalDate);
         }
 
         function setupNodeData(nodeData) {
@@ -582,68 +583,68 @@
             } = nodeData;
 
             // 设置选项和输入值
-            ['is_ddns', 'is_udp', 'status'].forEach(prop => nodeData[prop] && $(`#${prop}`).click());
-            ['is_display', 'detection_type', 'type'].forEach(prop => $(`input[name="${prop}"][value="${nodeData[prop]}"]`).click());
-            ['name', 'server', 'ip', 'ipv6', 'push_port', 'traffic_rate', 'speed_limit', 'client_limit', 'description', 'sort']
+            ["is_ddns", "is_udp", "status"].forEach(prop => nodeData[prop] && $(`#${prop}`).click());
+            ["is_display", "detection_type", "type"].forEach(prop => $(`input[name="${prop}"][value="${nodeData[prop]}"]`).click());
+            ["name", "server", "ip", "ipv6", "push_port", "traffic_rate", "speed_limit", "client_limit", "description", "sort"]
             .forEach(prop => $(`#${prop}`).val(nodeData[prop]));
-            ['level', 'rule_group_id', 'country_code', 'relay_node_id'].forEach(prop => $(`#${prop}`).selectpicker('val', nodeData[prop]));
-            $('#labels').selectpicker('val', labels.map(label => label.id));
+            ["level", "rule_group_id", "country_code", "relay_node_id"].forEach(prop => $(`#${prop}`).selectpicker("val", nodeData[prop]));
+            $("#labels").selectpicker("val", labels.map(label => label.id));
 
-            if (details?.next_renewal_date) $('#next_renewal_date').datepicker('update', details.next_renewal_date);
+            if (details?.next_renewal_date) $("#next_renewal_date").datepicker("update", details.next_renewal_date);
             if (details?.subscription_term) setSubscriptionTerm(details.subscription_term);
-            if (details?.renewal_cost) $('#renewal_cost').val(details.renewal_cost);
+            if (details?.renewal_cost) $("#renewal_cost").val(details.renewal_cost);
             calculateNextNextRenewalDate(); // 手动触发计算下下次续费日期
 
             if (relay_node_id) {
-                $('#relay_port').val(port);
+                $("#relay_port").val(port);
             } else {
                 setupNodeTypeHandlers(type, profile, port, tls_provider);
             }
 
             function setSubscriptionTerm(term) {
-                const [value, unit] = term.split(' ');
+                const [value, unit] = term.split(" ");
 
-                $('#subscription_term_value').val(value || '');
-                $('#subscription_term_unit').selectpicker('val', unit || 'day'); // 默认选择 day
+                $("#subscription_term_value").val(value || "");
+                $("#subscription_term_unit").selectpicker("val", unit || "day"); // 默认选择 day
             }
         }
 
         function setupDefaultValues() {
-            switchSetting('single');
-            switchSetting('is_ddns');
-            $('input[name="type"][value="0"]').click();
-            $('#status, #is_udp').click();
+            switchSetting("single");
+            switchSetting("is_ddns");
+            $("input[name='type'][value='0']").click();
+            $("#status, #is_udp").click();
         }
 
         function setupNodeTypeHandlers(type, profile, port, tls_provider) {
             const typeHandlers = {
-                0: () => $('#method').selectpicker('val', profile?.method || null),
+                0: () => $("#method").selectpicker("val", profile?.method || null),
                 1: setSSRValues,
                 2: setV2RayValues,
-                3: () => $('#trojan_port').val(port),
+                3: () => $("#trojan_port").val(port),
                 4: setSSRValues
             };
 
             typeHandlers[type] && typeHandlers[type]();
-            $('input[name="port"]').val(port);
+            $("input[name='port']").val(port);
 
             function setSSRValues() {
-                ['protocol', 'obfs'].forEach(prop => $(`#${prop}`).selectpicker('val', profile[prop] || null));
-                ['protocol_param', 'obfs_param'].forEach(prop => $(`#${prop}`).val(profile[prop] || null));
+                ["protocol", "obfs"].forEach(prop => $(`#${prop}`).selectpicker("val", profile[prop] || null));
+                ["protocol_param", "obfs_param"].forEach(prop => $(`#${prop}`).val(profile[prop] || null));
                 if (profile.passwd && port) {
-                    $('#single').click();
-                    $('#passwd').val(profile.passwd);
+                    $("#single").click();
+                    $("#passwd").val(profile.passwd);
                 }
             }
 
             function setV2RayValues() {
-                ['v2_alter_id', 'v2_host', 'v2_sni', 'v2_path'].forEach(prop => $(`#${prop}`).val(profile[prop] || null));
-                ['v2_net', 'v2_type'].forEach(prop => $(`#${prop}`).selectpicker('val', profile[prop] || null));
-                $('#v2_method').selectpicker('val', profile['method'] || null);
+                ["v2_alter_id", "v2_host", "v2_sni", "v2_path"].forEach(prop => $(`#${prop}`).val(profile[prop] || null));
+                ["v2_net", "v2_type"].forEach(prop => $(`#${prop}`).selectpicker("val", profile[prop] || null));
+                $("#v2_method").selectpicker("val", profile["method"] || null);
 
-                $('#v2_port').val(port);
-                profile.v2_tls && $('#v2_tls').click();
-                $('#tls_provider').val(tls_provider);
+                $("#v2_port").val(port);
+                profile.v2_tls && $("#v2_tls").click();
+                $("#tls_provider").val(tls_provider);
             }
         }
 
@@ -653,75 +654,75 @@
 
             // 获取所有非 hidden 的表单数据
             const data = Object.fromEntries(
-                $form.find('input:not([hidden]), select, textarea')
+                $form.find("input:not([hidden]), select, textarea")
                 .serializeArray()
                 .map(item => [item.name, item.value])
             );
 
             // 拼接 subscription_term
-            const termValue = $('#subscription_term_value').val();
-            const termUnit = $('#subscription_term_unit').val();
-            data['subscription_term'] = termValue ? `${termValue} ${termUnit}` : null;
+            const termValue = $("#subscription_term_value").val();
+            const termUnit = $("#subscription_term_unit").val();
+            data["subscription_term"] = termValue ? `${termValue} ${termUnit}` : null;
 
             // 将序列化的表单数据转换为 JSON 对象
-            $form.find('input[type="checkbox"]').each(function() {
+            $form.find("input[type='checkbox']").each(function() {
                 data[this.name] = this.checked ? 1 : 0;
             });
 
             // 处理多选 select
-            $form.find('select[multiple]').each(function() {
+            $form.find("select[multiple]").each(function() {
                 data[this.name] = $(this).val();
             });
 
             $.ajax({
                 url: '{{ isset($node) ? route('admin.node.update', $node) : route('admin.node.store') }}',
                 method: '{{ isset($node) ? 'PUT' : 'POST' }}',
-                contentType: 'application/json',
+                contentType: "application/json",
                 data: JSON.stringify(data),
                 success: function(ret) {
-                    if (ret.status === 'success') {
+                    if (ret.status === "success") {
                         swal.fire({
                             title: ret.message,
-                            icon: 'success',
+                            icon: "success",
                             timer: 1000,
-                            showConfirmButton: false,
+                            showConfirmButton: false
                         }).then(() => window.location.href =
                             '{{ route('admin.node.index') . (Request::getQueryString() ? '?' . Request::getQueryString() : '') }}');
                     } else {
                         swal.fire({
                             title: '{{ trans('common.error') }}',
                             text: ret.message,
-                            icon: 'error'
+                            icon: "error"
                         });
                     }
                 },
                 error: function(data) {
                     const errors = data.responseJSON?.errors;
                     if (errors) {
-                        const errorList = Object.values(errors).map(error => `<li>${error}</li>`).join('');
+                        const errorList = Object.values(errors).map(error => `<li>${error}</li>`).join("");
                         swal.fire({
                             title: '{{ trans('admin.hint') }}',
                             html: `<ul>${errorList}</ul>`,
-                            icon: 'error',
-                            confirmButtonText: '{{ trans('common.confirm') }}',
+                            icon: "error",
+                            confirmButtonText: '{{ trans('common.confirm') }}'
                         });
                     }
-                },
+                }
             });
         }
 
         function switchSetting(id) {
             const check = document.getElementById(id).checked;
-            if (id === 'single') {
-                $('.single-setting').toggle(check);
-                $('#single_port').attr({
-                    'hidden': !check,
-                    'required': check
+            if (id === "single") {
+                $(".single-setting").toggle(check);
+                $("#single_port").attr({
+                    "hidden": !check,
+                    "required": check
                 });
-                if (!check) $('#passwd').val('');
-            } else if (id === 'is_ddns') {
-                $('#ip, #ipv6').attr('readonly', check).val('');
-                $('#server').attr('required', check);
+                if (!check) $("#passwd").val("");
+            } else if (id === "is_ddns") {
+                $("#ip, #ipv6").attr("readonly", check).val("");
+                $("#server").attr("required", check);
             }
         }
 
@@ -729,29 +730,29 @@
         function updateServiceType() {
             const type = parseInt($(this).val());
             const settingsMap = {
-                0: ['.ss-setting'],
-                1: ['.ss-setting', '.ssr-setting'],
-                2: ['.v2ray-setting', '#v2_port'],
-                3: ['.trojan-setting', '#trojan_port'],
-                4: ['.ss-setting', '.ssr-setting'],
+                0: [".ss-setting"],
+                1: [".ss-setting", ".ssr-setting"],
+                2: [".v2ray-setting", "#v2_port"],
+                3: [".trojan-setting", "#trojan_port"],
+                4: [".ss-setting", ".ssr-setting"]
             };
 
-            $('.ss-setting, .ssr-setting, .v2ray-setting, .trojan-setting').hide();
-            Object.keys(settingsMap).forEach(key => $(settingsMap[key].join(',')).hide());
+            $(".ss-setting, .ssr-setting, .v2ray-setting, .trojan-setting").hide();
+            Object.keys(settingsMap).forEach(key => $(settingsMap[key].join(",")).hide());
             (settingsMap[type] || []).forEach(selector => $(selector).show());
         }
 
         function toggleObfsParam() {
-            const $obfsParam = $('.obfs_param');
-            $obfsParam.toggle($('#obfs').val() !== 'plain');
-            if ($('#obfs').val() === 'plain') $('#obfs_param').val('');
+            const $obfsParam = $(".obfs_param");
+            $obfsParam.toggle($("#obfs").val() !== "plain");
+            if ($("#obfs").val() === "plain") $("#obfs_param").val("");
         }
 
         function toggleRelayConfig() {
-            const hasRelay = $('#relay_node_id').val() !== '';
-            $('.relay-config').toggle(hasRelay);
-            $('.proxy-config').toggle(!hasRelay);
-            $('#relay_port').attr({
+            const hasRelay = $("#relay_node_id").val() !== "";
+            $(".relay-config").toggle(hasRelay);
+            $(".proxy-config").toggle(!hasRelay);
+            $("#relay_port").attr({
                 hidden: !hasRelay,
                 required: hasRelay
             });
@@ -760,37 +761,37 @@
         // 设置V2Ray详细设置
         function updateV2RaySettings() {
             const net = $(this).val();
-            const $type = $('.v2_type');
-            const $typeOption = $('#type_option');
-            const $host = $('.v2_host');
-            const $path = $('#v2_path');
+            const $type = $(".v2_type");
+            const $typeOption = $("#type_option");
+            const $host = $(".v2_host");
+            const $path = $("#v2_path");
             $type.show();
             $host.show();
             if (!$path.val()) {
-                $path.val('/' + string);
+                $path.val("/" + string);
             }
             switch (net) {
-                case 'ws':
-                case 'http':
+                case "ws":
+                case "http":
                     $type.hide();
                     break;
-                case 'domainsocket':
+                case "domainsocket":
                     $type.hide();
                     $host.hide();
                     break;
-                case 'quic':
-                    $typeOption.attr('disabled', false);
+                case "quic":
+                    $typeOption.attr("disabled", false);
                     if (!$path.val()) {
                         $path.val(string);
                     }
                     break;
-                case 'kcp':
-                case 'tcp':
+                case "kcp":
+                case "tcp":
                 default:
-                    $typeOption.attr('disabled', true);
+                    $typeOption.attr("disabled", true);
                     break;
             }
-            $('#v2_type').selectpicker('refresh');
+            $("#v2_type").selectpicker("refresh");
         }
 
         // 服务条款
@@ -809,8 +810,8 @@
             };
 
             swal.fire({
-                title: '[节点 user-config.json 配置示例]',
-                width: '36em',
+                title: "[节点 user-config.json 配置示例]",
+                width: "36em",
                 html: `
                     <div class="text-left">
                         <ol>
@@ -820,23 +821,23 @@
                         <pre class="bg-grey-800 text-white">${JSON.stringify(jsonConfig, null, 2)}</pre>
                     </div>
                 `,
-                icon: 'info',
-            })
+                icon: "info"
+            });
         };
 
         // 模式提示
         window.showPortsOnlyConfig = function() {
             swal.fire({
-                title: '[节点 user-config.json 配置示例]',
-                width: '36em',
+                title: "[节点 user-config.json 配置示例]",
+                width: "36em",
                 html: `
                   <ul class="bg-grey-800 text-white text-left">
                       <li>严格模式:"additional_ports_only": "true"</li>
                       <li>兼容模式:"additional_ports_only": "false"</li>
                   </ul>
                 `,
-                icon: 'info',
+                icon: "info"
             });
-        }
+        };
     </script>
 @endsection

+ 8 - 7
resources/views/auth/layouts.blade.php

@@ -118,23 +118,24 @@
             },
             insecure: true,
             unsupported: true,
-            api: 2024.07,
-        }
+            api: 2024.07
+        };
 
         function $buo_f() {
-            const e = document.createElement('script')
+            const e = document.createElement("script");
             e.src = "//browser-update.org/update.min.js";
             document.body.appendChild(e);
         }
+
         try {
-            document.addEventListener("DOMContentLoaded", $buo_f, false)
+            document.addEventListener("DOMContentLoaded", $buo_f, false);
         } catch (e) {
-            window.attachEvent("onload", $buo_f)
+            window.attachEvent("onload", $buo_f);
         }
     </script>
     @yield('javascript')
     <!-- 统计 -->
-    {!! sysConfig('website_analytics') !!}
+    {!! sysConfig('website_statistics_code') !!}
     <!-- 客服 -->
-    {!! sysConfig('website_customer_service') !!}
+    {!! sysConfig('website_customer_service_code') !!}
 @endsection

+ 1 - 1
resources/views/components/system/input-file.blade.php

@@ -2,7 +2,7 @@
 
 <div class="form-group col-lg-6">
     <div class="row">
-        <label class="col-form-label col-md-3" for="{{ $code }}">{{ trans('admin.system.' . $code) }}</label>
+        <label class="col-form-label col-md-3" for="{{ $code }}">{{ trans("admin.system.$code") }}</label>
         <div class="col-md-8">
             <input name="{{ $code }}" data-plugin="dropify" data-default-file="{{ asset($value ?? '/assets/images/default.png') }}" type="file" />
             <button class="btn btn-success float-right mt-10" type="submit">{{ trans('common.submit') }}</button>

+ 3 - 3
resources/views/components/system/input-limit.blade.php

@@ -2,7 +2,7 @@
 
 <div class="form-group col-lg-6">
     <div class="row">
-        <label class="col-md-3 col-form-label">{{ trans('admin.system.' . $code) }}</label>
+        <label class="col-md-3 col-form-label" for="{{ $code }}">{{ trans("admin.system.$code") }}</label>
         <div class="col-md-7">
             @isset($hcode)
                 <div class="input-group">
@@ -33,8 +33,8 @@
                     </div>
                 </div>
             @endisset
-            @if (trans('admin.system.hint.' . $code) !== 'admin.system.hint.' . $code)
-                <span class="text-help"> {!! trans('admin.system.hint.' . $code) !!} </span>
+            @if (trans("admin.system.hint.$code") !== "admin.system.hint.$code")
+                <span class="text-help"> {!! trans("admin.system.hint.$code") !!} </span>
             @endisset
     </div>
 </div>

+ 0 - 20
resources/views/components/system/input-test.blade.php

@@ -1,20 +0,0 @@
-@props(['type' => 'text', 'code', 'value', 'holder' => null, 'test'])
-<div class="form-group col-lg-6">
-    <div class="row">
-        <label class="col-md-3 col-form-label" for="{{ $code }}">{{ trans('admin.system.' . $code) }}</label>
-        <div class="col-md-6">
-            <div class="input-group">
-                <input class="form-control" id="{{ $code }}" type="{{ $type }}" value="{{ $value }}" placeholder="{!! $holder !!}" />
-                <span class="input-group-append">
-                    <button class="btn btn-primary" type="button" onclick="update('{{ $code }}')">{{ trans('common.update') }}</button>
-                </span>
-            </div>
-            @if (trans('admin.system.hint.' . $code) !== 'admin.system.hint.' . $code)
-                <span class="text-help"> {!! trans('admin.system.hint.' . $code) !!} @can('admin.test.notify')
-                        <a href="javascript:sendTestNotification('{{ $test }}');">[{{ trans('admin.system.notification.send_test') }}]</a>
-                    @endcan
-                </span>
-            @endif
-        </div>
-    </div>
-</div>

+ 22 - 0
resources/views/components/system/input-unit.blade.php

@@ -0,0 +1,22 @@
+@props(['type', 'key', 'value', 'units', 'unit'])
+<div class="form-group col-lg-6">
+    <div class="row">
+        <label class="col-md-3 col-form-label" for="{{ "{$type}_$key" }}">{{ trans("admin.system.tasks.$type.$key") }}</label>
+        <div class="col-md-6 input-group p-0">
+            <input class="form-control" name="{{ "$type:$key:value" }}" type="number" value="{{ $value }}" min="1">
+            @if (isset($units))
+                <select class="form-control" name="{{ "$type:$key:unit" }}" data-plugin=" selectpicker" data-style="btn-outline btn-primary">
+                    @foreach ($units as $u)
+                        <option value="{{ $u }}" {{ $unit === $u ? 'selected' : '' }}>
+                            {{ ucfirst(trans('validation.attributes.' . preg_replace('/s$/', '', $u))) }}
+                        </option>
+                    @endforeach
+                </select>
+            @elseif($unit)
+                <div class="input-group-append">
+                    <span class="input-group-text">{{ ucfirst(trans("validation.attributes.$unit")) }}</span>
+                </div>
+            @endif
+        </div>
+    </div>
+</div>

+ 20 - 9
resources/views/components/system/input.blade.php

@@ -1,22 +1,33 @@
-@props(['type' => 'text', 'code', 'value', 'holder' => null, 'url' => null])
+@props(['type' => 'text', 'code', 'value', 'holder' => null, 'url' => null, 'test' => null])
 
 <div class="form-group col-lg-6">
     <div class="row">
-        <label class="col-md-3 col-form-label" for="{{ $code }}">{{ trans('admin.system.' . $code) }}</label>
+        <label class="col-md-3 col-form-label" for="{{ $code }}">
+            {{ trans("admin.system.$code") }}
+        </label>
         <div class="col-md-6">
             <div class="input-group">
-                <input class="form-control" id="{{ $code }}" type="{{ $type }}" value="{{ $value }}" placeholder="{{ $holder }}" />
+                <input class="form-control" id="{{ $code }}" type="{{ $type }}" value="{{ $value }}" placeholder="{!! $holder !!}" />
                 <span class="input-group-append">
-                    <button class="btn btn-primary" type="button" onclick="update('{{ $code }}')">{{ trans('common.update') }}</button>
+                    <button class="btn btn-primary" type="button" onclick="update('{{ $code }}')">
+                        {{ trans('common.update') }}
+                    </button>
                 </span>
             </div>
-            @if (trans('admin.system.hint.' . $code) !== 'admin.system.hint.' . $code)
+            @if (trans("admin.system.hint.$code") !== "admin.system.hint.$code")
                 <span class="text-help">
-                    @if (isset($url))
-                        {!! trans('admin.system.hint.' . $code, ['url' => $url]) !!}
+                    @isset($url)
+                        {!! trans("admin.system.hint.$code", ['url' => $url]) !!}
                     @else
-                        {!! trans('admin.system.hint.' . $code) !!}
-                    @endif
+                        {!! trans("admin.system.hint.$code") !!}
+                    @endisset
+                    @isset($test)
+                        @can('admin.test.notify')
+                            <a href="javascript:sendTestNotification('{{ $test }}');">
+                                [{{ trans('admin.system.notification.send_test') }}]
+                            </a>
+                        @endcan
+                    @endisset
                 </span>
             @endif
         </div>

+ 3 - 3
resources/views/components/system/select.blade.php

@@ -2,7 +2,7 @@
 
 <div class="form-group col-lg-6">
     <div class="row">
-        <label class="col-md-3 col-form-label" for="{{ $code }}">{{ trans('admin.system.' . $code) }}</label>
+        <label class="col-md-3 col-form-label" for="{{ $code }}">{{ trans("admin.system.$code") }}</label>
         <div class="col-md-9">
             <select id="{{ $code }}" data-plugin="selectpicker" data-style="btn-outline btn-primary"
                     onchange="updateFromOther('{{ $multiple ? 'multiSelect' : 'select' }}','{{ $code }}')"
@@ -11,8 +11,8 @@
                     <option value="{{ $value }}">{{ $key }}</option>
                 @endforeach
             </select>
-            @if (trans('admin.system.hint.' . $code) !== 'admin.system.hint.' . $code)
-                <span class="text-help"> {!! trans('admin.system.hint.' . $code) !!} </span>
+            @if (trans("admin.system.hint.$code") !== "admin.system.hint.$code")
+                <span class="text-help"> {!! trans("admin.system.hint.$code") !!} </span>
             @endif
         </div>
     </div>

+ 7 - 6
resources/views/components/system/switch.blade.php

@@ -1,17 +1,18 @@
-@props(['code', 'check', 'url' => null])
+@props(['code', 'check', 'url' => null, 'feature' => null])
 
 <div class="form-group col-lg-6">
     <div class="row">
-        <label class="col-md-3 col-form-label" for="{{ $code }}">{{ trans('admin.system.' . $code) }}</label>
+        <label class="col-md-3 col-form-label" for="{{ $code }}">{{ trans("admin.system.$code") }}</label>
         <div class="col-md-9">
             <input id="{{ $code }}" data-plugin="switchery" type="checkbox" @if ($check) checked @endif
-                   onchange="updateFromOther('switch','{{ $code }}')">
-            @if (trans('admin.system.hint.' . $code) !== 'admin.system.hint.' . $code)
+                   @if ($feature) data-feature-toggle="{{ $feature }}" @endif
+                   onchange="updateFromOther('switch','{{ $code }}')" />
+            @if (trans("admin.system.hint.$code") !== "admin.system.hint.$code")
                 <span class="text-help">
                     @if (isset($url))
-                        {!! trans('admin.system.hint.' . $code, ['url' => $url]) !!}
+                        {!! trans("admin.system.hint.$code", ['url' => $url]) !!}
                     @else
-                        {!! trans('admin.system.hint.' . $code) !!}
+                        {!! trans("admin.system.hint.$code") !!}
                     @endif
                 </span>
             @endif

+ 3 - 2
resources/views/components/system/tab-pane.blade.php

@@ -1,6 +1,7 @@
-@props(['active' => false, 'id', 'slot'])
+@props(['active' => false, 'id', 'slot', 'feature' => null])
 
-<div class="tab-pane {{ $active ? 'active' : '' }}" id="{{ $id }}" role="tabpanel">
+<div class="tab-pane {{ $active ? 'active' : '' }}" id="{{ $id }}" role="tabpanel"
+     @if ($feature) data-feature="{{ $feature }}" @endif>
     <div class="form-row">
         {{ $slot }}
     </div>

+ 12 - 0
resources/views/components/system/task-group.blade.php

@@ -0,0 +1,12 @@
+@props(['type', 'items', 'units'])
+<div class="col-12 row" id="{{ "tasks_$type" }}">
+    <hr class="col-12" />
+    @foreach ($items as $key => $duration)
+        <x-system.input-unit type="{{ $type }}" :key="$key" :value="$duration['num']" :unit="$duration['unit']" :units="$units" />
+    @endforeach
+    <div class="col-12 text-center mt-md-15 mb-20">
+        <button class="btn btn-primary w-p25" type="button" onclick="updateJson('{{ "tasks_$type" }}')">
+            {{ trans('common.update') }}
+        </button>
+    </div>
+</div>

+ 3 - 3
resources/views/components/system/textarea.blade.php

@@ -2,7 +2,7 @@
 
 <div class="form-group col-lg-6">
     <div class="row">
-        <label class="col-md-3 col-form-label" for="{{ $code }}">{{ trans('admin.system.' . $code) }}</label>
+        <label class="col-md-3 col-form-label" for="{{ $code }}">{{ trans("admin.system.$code") }}</label>
         <div class="col-md-8">
             <div class="input-group">
                 <textarea class="form-control" id="{{ $code }}" rows={{ $row }}>{{ $value }}</textarea>
@@ -10,8 +10,8 @@
                     <button class="btn btn-primary" type="button" onclick="update('{{ $code }}')">{{ trans('common.update') }}</button>
                 </span>
             </div>
-            @if (trans('admin.system.hint.' . $code) !== 'admin.system.hint.' . $code)
-                <span class="text-help"> {!! trans('admin.system.hint.' . $code) !!} </span>
+            @if (trans("admin.system.hint.$code") !== "admin.system.hint.$code")
+                <span class="text-help"> {!! trans("admin.system.hint.$code") !!} </span>
             @endisset
     </div>
 </div>

+ 10 - 10
resources/views/user/components/payment/default.blade.php

@@ -19,7 +19,7 @@
                             @if ($days !== 0)
                                 <li class="list-group-item">{{ trans('common.available_date') . ': ' . $days . trans_choice('common.days.attribute', 1) }}</li>
                             @endif
-                            <li class="list-group-item"> {!! trans('user.payment.close_tips', ['minutes' => config('tasks.close.orders')]) !!}</li>
+                            <li class="list-group-item"> {!! trans('user.payment.close_tips', ['minutes' => (time() - strtotime(sysConfig('tasks_close.orders'))) / 60]) !!}</li>
                         </ul>
                     </div>
                     <div class="col-auto mx-auto">
@@ -45,11 +45,11 @@
             const options = {
                 text: @json($payment->url),
                 backgroundImage: '{{ asset($pay_type_icon) }}',
-                autoColor: true,
+                autoColor: true
             };
 
             // Create QRCode Object
-            new QRCode(document.getElementById('qrcode'), options);
+            new QRCode(document.getElementById("qrcode"), options);
         </script>
     @endif
 
@@ -57,34 +57,34 @@
         // 检查支付单状态
         const r = window.setInterval(function() {
             $.ajax({
-                method: 'GET',
+                method: "GET",
                 url: '{{ route('orderStatus') }}',
                 data: {
                     trade_no: '{{ $payment->trade_no }}'
                 },
-                dataType: 'json',
+                dataType: "json",
                 success: function(ret) {
                     window.clearInterval();
-                    if (ret.status === 'success') {
+                    if (ret.status === "success") {
                         swal.fire({
                             title: ret.message,
-                            icon: 'success',
+                            icon: "success",
                             timer: 1500,
                             showConfirmButton: false
                         }).then(() => {
                             window.location.href = '{{ route('invoice.index') }}';
                         });
-                    } else if (ret.status === 'error') {
+                    } else if (ret.status === "error") {
                         swal.fire({
                             title: ret.message,
-                            icon: 'error',
+                            icon: "error",
                             timer: 1500,
                             showConfirmButton: false
                         }).then(() => {
                             window.location.href = '{{ route('invoice.index') }}';
                         });
                     }
-                },
+                }
             });
         }, 3000);
     </script>

+ 1 - 6
resources/views/user/components/purchase.blade.php

@@ -14,12 +14,7 @@
     </button>
 @endif
 @if (sysConfig('is_otherPay'))
-    @if (str_contains(sysConfig('is_otherPay'), 'bitpayx'))
-        <button class="btn btn-round btn-outline-default mt-2" onclick="pay('bitpayx','4')">
-            <img src="/assets/images/payment/btc.svg" alt="{{ trans('common.payment.crypto') }}" height="36px" />
-            <span class="font-size-24 black"> {{ trans('common.payment.crypto') }} </span>
-        </button>
-    @elseif(str_contains(sysConfig('is_otherPay'), 'cryptomus'))
+    @if (str_contains(sysConfig('is_otherPay'), 'cryptomus'))
         <button class="btn btn-round btn-outline-default mt-2" onclick="pay('cryptomus','4')">
             <img src="/assets/images/payment/btc.svg" alt="{{ trans('common.payment.crypto') }}" height="36px" />
             <span class="font-size-24 black"> {{ trans('common.payment.crypto') }} </span>

+ 59 - 59
resources/views/user/index.blade.php

@@ -17,7 +17,7 @@
                             <i class="wb-heart red-500" aria-hidden="true"></i>
                         </button>
                         <span class="font-weight-400">{{ trans('user.account.status') }}</span>
-                        @if (sysConfig('is_checkin'))
+                        @if (sysConfig('checkin_interval'))
                             <button class="btn btn-md btn-round btn-info float-right" onclick="checkIn()">
                                 <i class="wb-star yellow-400 mr-5" aria-hidden="true"></i>
                                 {{ trans('user.home.attendance.attribute') }}
@@ -50,7 +50,7 @@
                             @elseif($user['ban_time'])
                                 <i class="wb-alert orange-400 font-size-40 mr-10"></i>
                                 <span class="font-size-40 font-weight-100">{{ trans('common.status.limited') }}</span>
-                                <p class="font-weight-300 m-0 orange-500">{!! trans('user.account.reason.overused', ['data' => sysConfig('traffic_ban_value')]) !!}</p>
+                                <p class="font-weight-300 m-0 orange-500">{!! trans('user.account.reason.overused', ['data' => sysConfig('traffic_abuse_limit')]) !!}</p>
                             @else
                                 <i class="wb-help red-400 font-size-40 mr-10"></i>
                                 <span class="font-size-40 font-weight-100">{{ trans('common.status.disabled') }}</span>
@@ -323,26 +323,26 @@
             swal.fire({
                 title: '{{ trans('common.warning') }}',
                 text: '{{ trans('user.subscribe.exchange_warning') }}',
-                icon: 'warning',
+                icon: "warning",
                 showCancelButton: true,
                 cancelButtonText: '{{ trans('common.close') }}',
-                confirmButtonText: '{{ trans('common.confirm') }}',
+                confirmButtonText: '{{ trans('common.confirm') }}'
             }).then((result) => {
                 if (result.value) {
                     $.post('{{ route('changeSub') }}', {
                         _token: '{{ csrf_token() }}'
                     }, function(ret) {
-                        if (ret.status === 'success') {
+                        if (ret.status === "success") {
                             swal.fire({
                                 title: ret.message,
-                                icon: 'success',
+                                icon: "success",
                                 timer: 1000,
-                                showConfirmButton: false,
+                                showConfirmButton: false
                             }).then(() => window.location.reload());
                         } else {
                             swal.fire({
                                 title: ret.message,
-                                icon: 'error'
+                                icon: "error"
                             }).then(() => window.location.reload());
                         }
                     });
@@ -350,48 +350,48 @@
             });
         }
 
-        const clipboard = new ClipboardJS('.mt-clipboard', {
+        const clipboard = new ClipboardJS(".mt-clipboard", {
             text: function(trigger) {
                 let base = @json($user['sub_url']);
-                const client = $('#client').val();
-                const subType = $('#subType').val();
+                const client = $("#client").val();
+                const subType = $("#subType").val();
                 if (subType && client) {
-                    base += '?target=' + client + '&type=' + subType;
+                    base += "?target=" + client + "&type=" + subType;
                 } else if (subType) {
-                    base += '?type=' + subType;
+                    base += "?type=" + subType;
                 } else if (client) {
-                    base += '?target=' + client;
+                    base += "?target=" + client;
                 }
                 return base;
-            },
+            }
         });
-        clipboard.on('success', function() {
+        clipboard.on("success", function() {
             swal.fire({
                 title: '{{ trans('common.copy.success') }}',
-                icon: 'success',
+                icon: "success",
                 timer: 1300,
-                showConfirmButton: false,
+                showConfirmButton: false
             });
         });
-        clipboard.on('error', function() {
+        clipboard.on("error", function() {
             swal.fire({
                 title: '{{ trans('common.copy.failed') }}',
-                icon: 'error',
+                icon: "error",
                 timer: 1500,
-                showConfirmButton: false,
+                showConfirmButton: false
 
             });
         });
 
-        @if (sysConfig('is_checkin'))
+        @if (sysConfig('checkin_interval'))
             function checkIn() { // 签到
                 $.post('{{ route('checkIn') }}', {
                     _token: '{{ csrf_token() }}'
                 }, function(ret) {
-                    if (ret.status === 'success') {
-                        swal.fire(ret.title, ret.message, 'success').then(() => window.location.reload());
+                    if (ret.status === "success") {
+                        swal.fire(ret.title, ret.message, "success").then(() => window.location.reload());
                     } else {
-                        swal.fire(ret.title, ret.message, 'error');
+                        swal.fire(ret.title, ret.message, "error");
                     }
                 });
             }
@@ -404,40 +404,40 @@
                     x: {
                         ticks: {
                             callback: function(value) {
-                                return this.getLabelForValue(value) + ' ' + tail;
-                            },
+                                return this.getLabelForValue(value) + " " + tail;
+                            }
                         },
                         grid: {
-                            display: false,
-                        },
+                            display: false
+                        }
                     },
                     y: {
                         ticks: {
                             callback: function(value) {
-                                return this.getLabelForValue(value) + ' GB';
-                            },
+                                return this.getLabelForValue(value) + " GB";
+                            }
                         },
                         grid: {
-                            display: false,
+                            display: false
                         },
-                        min: 0,
-                    },
+                        min: 0
+                    }
                 },
                 plugins: {
                     legend: false,
                     tooltip: {
-                        mode: 'index',
+                        mode: "index",
                         intersect: false,
                         callbacks: {
                             title: function(context) {
-                                return context[0].label + ' ' + tail;
+                                return context[0].label + " " + tail;
                             },
                             label: function(context) {
-                                return context.parsed.y + ' GB';
-                            },
-                        },
-                    },
-                },
+                                return context.parsed.y + " GB";
+                            }
+                        }
+                    }
+                }
             };
         }
 
@@ -445,39 +445,39 @@
             return {
                 labels: label,
                 datasets: [{
-                    backgroundColor: 'rgba(184, 215, 255)',
-                    borderColor: 'rgba(184, 215, 255)',
+                    backgroundColor: "rgba(184, 215, 255)",
+                    borderColor: "rgba(184, 215, 255)",
                     data: data,
-                    tension: 0.4,
-                }],
+                    tension: 0.4
+                }]
             };
         }
 
-        new Chart(document.getElementById('dailyChart'), {
-            type: 'line',
+        new Chart(document.getElementById("dailyChart"), {
+            type: "line",
             data: datasets(@json($dayHours), @json($trafficHourly)),
-            options: common_options(@json(trans_choice('common.hour', 2))),
+            options: common_options(@json(trans_choice('common.hour', 2)))
         });
 
-        new Chart(document.getElementById('monthlyChart'), {
-            type: 'line',
+        new Chart(document.getElementById("monthlyChart"), {
+            type: "line",
             data: datasets(@json($monthDays), @json($trafficDaily)),
-            options: common_options(@json(trans_choice('common.days.attribute', 2))),
+            options: common_options(@json(trans_choice('common.days.attribute', 2)))
         });
 
         @if ($user['ban_time']) // 封禁倒计时
             const banedTime = new Date("{{ $user['ban_time'] }}").getTime();
-            countDown(banedTime, 'banedTime', true);
+            countDown(banedTime, "banedTime", true);
             setInterval(function() {
-                countDown(banedTime, 'banedTime', true);
+                countDown(banedTime, "banedTime", true);
             }, 1000);
         @endif
 
         @if (isset($resetDays) && $resetDays === 0) // 重置日倒计时
             const resetTime = new Date("{{ date('Y-m-d 00:00', strtotime('tomorrow')) }}").getTime();
-            countDown(resetTime, 'restTime');
+            countDown(resetTime, "restTime");
             setInterval(function() {
-                countDown(resetTime, 'restTime');
+                countDown(resetTime, "restTime");
             }, 60000);
         @endif
 
@@ -485,15 +485,15 @@
             const distance = endTime - new Date().getTime();
             const hour = Math.floor(distance % 86400000 / 3600000);
             const minute = Math.floor((distance % 3600000) / 60000);
-            let string = '';
+            let string = "";
             if (hour) {
-                string += hour + ' ' + @json(trans_choice('common.hour', 1));
+                string += hour + " " + @json(trans_choice('common.hour', 1));
             }
             if (minute) {
-                string += ' ' + minute + ' ' + @json(ucfirst(trans('validation.attributes.minute')));
+                string += " " + minute + " " + @json(ucfirst(trans('validation.attributes.minute')));
             }
             if (seconds) {
-                string += ' ' + Math.floor((distance % 60000) / 1000) + ' ' + @json(ucfirst(trans('validation.attributes.second')));
+                string += " " + Math.floor((distance % 60000) / 1000) + " " + @json(ucfirst(trans('validation.attributes.second')));
             }
             document.getElementById(id).innerHTML = string;
 

+ 17 - 16
resources/views/user/layouts.blade.php

@@ -222,53 +222,54 @@
             },
             insecure: true,
             unsupported: true,
-            api: 2024.07,
-        }
+            api: 2024.07
+        };
 
         function $buo_f() {
-            const e = document.createElement('script')
+            const e = document.createElement("script");
             e.src = "//browser-update.org/update.min.js";
             document.body.appendChild(e);
         }
+
         try {
-            document.addEventListener("DOMContentLoaded", $buo_f, false)
+            document.addEventListener("DOMContentLoaded", $buo_f, false);
         } catch (e) {
-            window.attachEvent("onload", $buo_f)
+            window.attachEvent("onload", $buo_f);
         }
     </script>
     @yield('javascript')
     @if (Session::has('admin'))
         <script>
-            $('#return_to_admin').click(function() {
+            $("#return_to_admin").click(function() {
                 $.ajax({
-                    method: 'POST',
+                    method: "POST",
                     url: '{{ route('switch') }}',
                     data: {
-                        '_token': '{{ csrf_token() }}'
+                        "_token": '{{ csrf_token() }}'
                     },
-                    dataType: 'json',
+                    dataType: "json",
                     success: function(ret) {
                         swal.fire({
                             title: ret.message,
-                            icon: 'success',
+                            icon: "success",
                             timer: 1000,
-                            showConfirmButton: false,
+                            showConfirmButton: false
                         }).then(() => window.location.href = '{{ route('admin.index') }}');
                     },
                     error: function(ret) {
                         swal.fire({
                             title: ret.message,
-                            icon: 'error',
+                            icon: "error",
                             timer: 1500,
-                            showConfirmButton: false,
+                            showConfirmButton: false
                         });
-                    },
+                    }
                 });
             });
         </script>
     @endif
     <!-- 统计 -->
-    {!! sysConfig('website_analytics') !!}
+    {!! sysConfig('website_statistics_code') !!}
     <!-- 客服 -->
-    {!! sysConfig('website_customer_service') !!}
+    {!! sysConfig('website_customer_service_code') !!}
 @endsection

+ 1 - 1
routes/web.php

@@ -11,7 +11,7 @@ use App\Http\Controllers\User\SubscribeController;
 if (config('app.key') && config('settings')) {
     Route::domain(sysConfig('subscribe_domain') ?: sysConfig('website_url'))->get('s/{code}', [SubscribeController::class, 'getSubscribeByCode'])->name('sub'); // 节点订阅地址
 
-    Route::domain(sysConfig('website_callback_url') ?: sysConfig('website_url'))->match(['get', 'post'], 'callback/notify', [PaymentController::class, 'notify'])->name('payment.notify'); //支付回调
+    Route::domain(sysConfig('payment_callback_url') ?: sysConfig('website_url'))->match(['get', 'post'], 'callback/notify', [PaymentController::class, 'notify'])->name('payment.notify'); //支付回调
 }
 
 Route::post('api/telegram/webhook', [TelegramController::class, 'webhook'])->middleware('telegram'); // Telegram fallback

+ 32 - 5
scripts/download_utils.sh

@@ -19,6 +19,8 @@ check_and_install() {
       sudo pacman -S "$pkg"
     elif command -v zypper >/dev/null 2>&1; then
       sudo zypper install -y "$pkg"
+    elif [[ -f /etc/alpine-release ]]; then
+      sudo apk add --no-cache "$pkg"
     else
       echo -e "\e[31m无法安装 $pkg,不支持的 Linux 发行版\e[0m"
       exit 1
@@ -29,6 +31,8 @@ check_and_install() {
 # 获取 GitHub 仓库的最新标签
 get_tag() {
   local repo=$1
+    local headers=("-H" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36")  # 合法 User-Agent
+
   curl -fsSL "https://api.github.com/repos/$repo/releases/latest" | jq -r '.tag_name'
 }
 
@@ -50,7 +54,7 @@ download_file() {
       echo -e "\e[31mURL $url 不存在\e[0m"
       return 2
     fi
-    if ! curl -L -m 60 -o "$tmp_file" "$url"; then
+    if ! curl -L -m 60 -H "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36" --retry 3 --retry-delay 5 -o "$tmp_file" "$url"; then
       echo -e "\e[31m下载 $name 失败\e[0m"
       rm -f "$tmp_file"
       return 1
@@ -76,17 +80,40 @@ download_file() {
 
 # 处理文件下载
 process_files() {
-  local json="{}"
+  # 读取当前版本信息(如果存在)
+  local current_versions
+  if [ -f "$VERSION_FILE" ]; then
+    current_versions=$(cat "$VERSION_FILE")
+  else
+    current_versions="{}"
+  fi
+
+  # 创建临时版本文件
+  local tmp_version_file="/tmp/version.json.tmp"
+  echo "$current_versions" > "$tmp_version_file"
+
+  # 遍历所有文档
   for doc in "${!docs[@]}"; do
     if [[ $doc == *_name ]]; then
       local name=${docs[$doc]}
       local version=${docs[${doc/_name/_version}]}
       local url=${docs[${doc/_name/_url}]}
 
-      download_file "$name" "$version" "$url"
-      json=$(jq -r --arg name "$name" --arg version "$version" '.[$name]=$version' <<<"$json")
+      # 下载文件并检查状态
+      if download_file "$name" "$version" "$url"; then
+        # 下载成功,更新临时版本文件
+        jq -r --arg name "$name" --arg version "$version" '.[$name]=$version' \
+          <"$tmp_version_file" > "$tmp_version_file.tmp"
+        mv "$tmp_version_file.tmp" "$tmp_version_file"
+      else
+        # 下载失败,保留旧版本(如果存在)
+        local old_version=$(jq -r ".[\"$name\"]" <"$tmp_version_file" 2>/dev/null)
+        echo -e "\e[33m[跳过] $name 版本保持为 ${old_version:-未安装}\e[0m"
+      fi
     fi
   done
 
-  echo "$json" >"$VERSION_FILE"
+  # 原子化替换原版本文件
+  mv "$tmp_version_file" "$VERSION_FILE"
+  echo -e "\e[32m版本文件已更新(仅包含成功下载的条目)\e[0m"
 }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません