Răsfoiți Sursa

Improve batch processing and shop logic

- Use chunkById() with default chunk size for batch processing;
- Improve plan/package expiration and prepaid activation logic;
- Refactor user–node permission update logic
- Optimize OrderService state handling
- Minor fixes (null handling, query order, comments)
BrettonYe 1 săptămână în urmă
părinte
comite
dc3277e0d6

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

@@ -24,8 +24,8 @@ class ServiceTimer extends Command
 
     private function expiredPlan(): void
     {
-        Order::activePlan()->where('expired_at', '<=', now())->chunk(sysConfig('tasks_chunk'), function ($orders) {
-            $orders->each->expired(); // 过期订单
+        Order::activePlan()->where('expired_at', '<=', now())->chunkById((int) sysConfig('tasks_chunk', 3000), function ($orders) {
+            $orders->each->expired(); // 过期订单,触发 Observer
         });
     }
 }

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

@@ -50,7 +50,7 @@ class TaskAuto extends Command
             $query->recentUnPay(); // 关闭超时未支付本地订单
         })->orWhere(function (Builder $query) {
             $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) {
+        })->chunkById((int) sysConfig('tasks_chunk', 3000), function ($orders) {
             $orders->each->close();
         });
     }
@@ -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_rate_limit'))->chunk(sysConfig('tasks_chunk'), function ($users) use ($banMsg, $dirtyWorks) {
+        }, '>=', sysConfig('subscribe_rate_limit'))->chunkById((int) sysConfig('tasks_chunk', 3000), 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(sysConfig('tasks_chunk'), function ($subscribes) {
+        UserSubscribe::whereStatus(0)->where('ban_time', '<=', time())->chunkById((int) sysConfig('tasks_chunk', 3000), 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(sysConfig('tasks_chunk'), function ($users) {
+        User::activeUser()->whereRaw('u + d >= transfer_enable')->chunkById((int) sysConfig('tasks_chunk', 3000), function ($users) {
             $users->each(function ($user) {
                 $user->update(['enable' => 0]);
                 $user->banedLogs()->create(['description' => __('[Auto Task] Blocked service: Run out of traffic')]);
@@ -110,7 +110,7 @@ class TaskAuto extends Command
             $userService = new UserService;
 
             User::activeUser()->whereBanTime(null)->where('t', '>=', strtotime('-5 minutes')) // 只检测最近5分钟有流量使用的用户
-                ->chunk(sysConfig('tasks_chunk'), function ($users) use ($userService, $ban_time, $trafficBanTime) {
+                ->chunkById((int) sysConfig('tasks_chunk', 3000), 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(sysConfig('tasks_chunk'), function ($users) {
+        User::bannedUser()->where('ban_time', '<', time())->chunkById((int) sysConfig('tasks_chunk', 3000), 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(sysConfig('tasks_chunk'), function ($users) {
+        User::bannedUser()->whereBanTime(null)->where('expired_at', '>=', date('Y-m-d'))->whereRaw('u + d < transfer_enable')->chunkById((int) sysConfig('tasks_chunk', 3000), function ($users) {
             $users->each(function ($user) {
                 $user->update(['enable' => 1]);
                 $user->banedLogs()->create(['description' => __('[Auto Task] Unblocked Service: Account has available data traffic')]);

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

@@ -60,7 +60,7 @@ class TaskDaily extends Command
         }
 
         User::activeUser()->where('expired_at', '<', date('Y-m-d')) // 过期
-            ->chunk(sysConfig('tasks_chunk'), function ($users) use ($banMsg, $dirtyWorks) {
+            ->chunkById((int) sysConfig('tasks_chunk', 3000), function ($users) use ($banMsg, $dirtyWorks) {
                 $users->each(function ($user) use ($banMsg, $dirtyWorks) {
                     $user->update($dirtyWorks);
                     Helpers::addUserTrafficModifyLog($user->id, $user->transfer_enable, 0, $banMsg);
@@ -74,7 +74,7 @@ class TaskDaily extends Command
         $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', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_close.tickets'))))->chunk(sysConfig('tasks_chunk'), function ($tickets) use ($closeTicketsHours) {
+        })->where('updated_at', '<=', date('Y-m-d H:i:s', strtotime(sysConfig('tasks_close.tickets'))))->chunkById((int) sysConfig('tasks_chunk', 3000), 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),
@@ -91,7 +91,7 @@ class TaskDaily extends Command
             $query->activePlan();
         })->with(['orders' => function ($query) {
             $query->activePlan();
-        }])->chunk(sysConfig('tasks_chunk'), function ($users) {
+        }])->chunkById((int) sysConfig('tasks_chunk', 3000), function ($users) {
             $users->each(function ($user) {
                 $user->orders()->activePackage()->update(['is_expire' => 1]); // 过期生效中的加油包
                 $order = $user->orders->first(); // 取出用户正在使用的套餐
@@ -126,7 +126,7 @@ class TaskDaily extends Command
             'dataFlowLogs' => function ($query) use ($start, $end) {
                 $query->whereBetween('log_time', [$start, $end]);
             },
-        ])->chunk(sysConfig('tasks_chunk'), function ($users) use ($created_at) {
+        ])->chunkById((int) sysConfig('tasks_chunk', 3000), function ($users) use ($created_at) {
             foreach ($users as $user) {
                 $dataFlowLogs = $user->dataFlowLogs->groupBy('node_id');
 
@@ -174,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(sysConfig('tasks_chunk'), function ($nodes) use ($created_at) {
+        ])->chunkById((int) sysConfig('tasks_chunk', 3000), function ($nodes) use ($created_at) {
             foreach ($nodes as $node) {
                 $node->dailyDataFlows()->create([
                     'u' => $node->u_sum,

+ 3 - 3
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_abuse_threshold = (int) sysConfig('traffic_abuse_limit') * GiB;
+        $traffic_abuse_threshold = (int) sysConfig('traffic_abuse_limit', 20) * 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(sysConfig('tasks_chunk'), function ($users) use ($traffic_abuse_threshold, $created_at, $data_anomaly_notification) {
+        ])->chunkById((int) sysConfig('tasks_chunk', 3000), function ($users) use ($traffic_abuse_threshold, $created_at, $data_anomaly_notification) {
             foreach ($users as $user) {
                 $dataFlowLogs = $user->dataFlowLogs->groupBy('node_id');
 
@@ -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(sysConfig('tasks_chunk'), function ($nodes) use ($created_at) {
+        ])->chunkById((int) sysConfig('tasks_chunk', 3000), function ($nodes) use ($created_at) {
             foreach ($nodes as $node) {
                 $node->hourlyDataFlows()->create(['u' => $node->u_sum, 'd' => $node->d_sum, 'created_at' => $created_at]);
             }

+ 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(sysConfig('tasks_chunk'), function ($users) {
+            ->chunkById((int) sysConfig('tasks_chunk', 3000), 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(sysConfig('tasks_chunk'), function ($users) use ($trafficWarningPercent) {
+        User::activeUser()->where('transfer_enable', '>', 0)->chunkById((int) sysConfig('tasks_chunk', 3000), function ($users) use ($trafficWarningPercent) {
             foreach ($users as $user) {
                 // 用户账号不是邮箱的跳过
                 if (filter_var($user->username, FILTER_VALIDATE_EMAIL) === false) {

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

@@ -223,7 +223,7 @@ class LogsController extends Controller
             });
         });
 
-        return view('admin.logs.userTraffic', ['userTrafficLogs' => $query->latest()->paginate(15)->appends($request->except('page'))]);
+        return view('admin.logs.userTraffic', ['userTrafficLogs' => $query->orderByDesc('id')->paginate(15)->appends($request->except('page'))]);
     }
 
     public function userOnlineIPList(Request $request): View

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

@@ -146,9 +146,9 @@ class NodeController extends Controller
                     'method' => $info['method'],
                     'protocol' => $info['protocol'],
                     'obfs' => $info['obfs'],
-                    'obfs_param' => $info['obfs_param'],
-                    'protocol_param' => $info['protocol_param'],
-                    'passwd' => $info['passwd'],
+                    'obfs_param' => $info['obfs_param'] ?? null,
+                    'protocol_param' => $info['protocol_param'] ?? null,
+                    'passwd' => $info['passwd'] ?? null,
                 ];
                 break;
         }

+ 2 - 2
app/Http/Controllers/Admin/UserController.php

@@ -223,7 +223,7 @@ class UserController extends Controller
         $data['transfer_enable'] *= GiB;
         $data['enable'] = $data['status'] < 0 ? 0 : $data['enable'];
         $data['expired_at'] = $data['expired_at'] ?? date('Y-m-d', strtotime('next year'));
-        if ($data['remark']) {
+        if (! empty($data['remark'])) {
             $data['remark'] = str_replace(['atob', 'eval'], '', $data['remark']);
         }
 
@@ -253,7 +253,7 @@ class UserController extends Controller
                 $data['password'] = $password;
             }
 
-            if ($user->transfer_enable !== $data['transfer_enable']) {
+            if ($user->transfer_enable !== (int) $data['transfer_enable']) {
                 Helpers::addUserTrafficModifyLog($user->id, $user->transfer_enable, $data['transfer_enable'], trans('Manually edit in dashboard.'));
             }
 

+ 4 - 15
app/Http/Controllers/User/InvoiceController.php

@@ -26,23 +26,12 @@ class InvoiceController extends Controller
     public function activate(): JsonResponse
     { // 激活套餐
         $activePlan = Order::userActivePlan()->first();
-        if ($activePlan) {
-            if ($activePlan->expired()) { // 关闭先前套餐后,新套餐自动运行
-                if (Order::userActivePlan()->exists()) {
-                    return response()->json(['status' => 'success', 'message' => trans('common.active_item', ['attribute' => trans('common.success')])]);
-                }
-
-                return response()->json(['status' => 'success', 'message' => trans('common.close')]);
+        if ($activePlan && $activePlan->expired()) { // 关闭先前套餐后,新套餐自动运行
+            if (Order::userActivePlan()->exists()) {
+                return response()->json(['status' => 'success', 'message' => trans('common.active_item', ['attribute' => trans('common.success')])]);
             }
-        } else {
-            $prepaidPlan = Order::userPrepay()->first();
-            if ($prepaidPlan) { // 关闭先前套餐后,新套餐自动运行
-                if ($prepaidPlan->complete()) {
-                    return response()->json(['status' => 'success', 'message' => trans('common.active_item', ['attribute' => trans('common.success')])]);
-                }
 
-                return response()->json(['status' => 'success', 'message' => trans('common.close')]);
-            }
+            return response()->json(['status' => 'success', 'message' => trans('common.close')]);
         }
 
         return response()->json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('common.close')])]);

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

@@ -34,9 +34,7 @@ class UserController extends Controller
             'remainDays' => $userService->getRemainingDays(),
             'resetDays' => $userService->getResetDays(),
             'unusedPercent' => $userService->getUnusedTrafficPercent(),
-            'announcements' => cache()->remember('announcements_'.app()->getLocale(), 300, function () {
-                return Article::type(2)->lang()->latest()->simplePaginate(1); // 公告缓存 5 分钟
-            }), // 公告
+            'announcements' => Article::type(2)->lang()->latest()->simplePaginate(1), // 公告
             'isTrafficWarning' => $userService->isTrafficWarning(), // 流量异常判断
             'paying_user' => $userService->isActivePaying(), // 付费用户判断
             'user' => $user->only(['sub_url', 'unused_traffic', 'expiration_date', 'ban_time']),

+ 60 - 43
app/Observers/OrderObserver.php

@@ -16,57 +16,74 @@ class OrderObserver
     public function updated(Order $order): void
     {
         $changes = $order->getChanges();
-        // 套餐订单-流量包订单互联
-        if (Arr::has($changes, 'is_expire') && $changes['is_expire'] === 1 && $order->goods->type === 2) {
+
+        // 套餐订单-流量包订单互联:订单过期时直接处理
+        if (Arr::has($changes, 'is_expire') && $changes['is_expire'] === 1 && $order->goods && $order->goods->type === 2) {
             $user = $order->user;
-            $user->update([ // 清理全部流量,重置重置日期和等级
-                'u' => 0,
-                'd' => 0,
-                'transfer_enable' => 0,
-                'reset_time' => null,
-                'level' => 0,
-                'enable' => 0,
-            ]);
-            Helpers::addUserTrafficModifyLog($user->id, $user->transfer_enable, 0, trans('[Service Timer] Service Expiration'), $order->id);
+            $oldTransferEnable = $user->transfer_enable;
 
-            Order::userActivePackage($order->user_id)->update(['is_expire' => 1]); // 过期生效中的加油包
-            $this->activatePrepaidPlan($order->user_id); // 激活预支付套餐
-        }
+            // 过期加油包
+            Order::userActivePackage($order->user_id)->update(['is_expire' => 1]);
 
-        if (Arr::has($changes, 'status')) {
-            if ($changes['status'] === -1) { // 本地订单-在线订单 关闭互联
-                if ($order->payment) {
-                    $order->payment->close(); // 关闭在线订单
-                }
+            // 检查是否有预支付订单
+            $prepaidOrder = Order::userPrepay($order->user_id)->first();
 
-                if ($order->coupon) { // 退回优惠券
-                    $this->returnCoupon($order, $order->coupon);
-                }
+            if ($prepaidOrder) {
+                (new OrderService($prepaidOrder))->activatePlan();
+            } else {
+                // 无预支付订单:仅清理用户
+                $user->update([
+                    'u' => 0,
+                    'd' => 0,
+                    'transfer_enable' => 0,
+                    'reset_time' => null,
+                    'level' => 0,
+                    'enable' => 0,
+                ]);
 
-                if ($order->goods && $order->goods->type === 2 && $order->getOriginal('status') === 2 && Order::userPrepay($order->user_id)->exists()) { // 下一个套餐
-                    $this->activatePrepaidPlan($order->user_id);
-                } else {
-                    (new OrderService($order))->refreshAccountExpiration();
-                }
-            } elseif ($changes['status'] === 1) { // 待确认支付 通知管理
-                Notification::send(User::find(1), new PaymentConfirm($order));
-            } elseif ($changes['status'] === 2 && $order->getOriginal('status') !== 3) { // 本地订单-在线订单 支付成功互联
-                (new OrderService($order))->receivedPayment();
-            } elseif ($changes['status'] === 3) {
-                if (Order::userActivePlan($order->user_id)->doesntExist()) {
-                    $this->activatePrepaidPlan($order->user_id);
-                } else {
-                    (new OrderService($order))->refreshAccountExpiration();
-                }
+                Helpers::addUserTrafficModifyLog($user->id, $oldTransferEnable, 0, trans('[Service Timer] Service Expiration'), $order->id);
             }
         }
-    }
 
-    private function activatePrepaidPlan(int $user_id): void
-    { // 激活[预支付订单]
-        $prepaidOrder = Order::userPrepay($user_id)->first(); // 检查该订单对应用户是否有预支付套餐
-        if ($prepaidOrder) {
-            (new OrderService($prepaidOrder))->activatePrepaidPlan(); // 激活预支付套餐
+        if (Arr::has($changes, 'status')) {
+            $originalStatus = $order->getOriginal('status');
+
+            switch ($changes['status']) {
+                case -1: // 订单关闭
+                    if ($order->payment) {
+                        $order->payment->close();
+                    }
+
+                    if ($order->coupon) {
+                        $this->returnCoupon($order, $order->coupon);
+                    }
+
+                    if ($order->goods && $order->goods->type === 2 && $originalStatus === 2 && Order::userPrepay($order->user_id)->exists()) {
+                        $prepaidOrder = Order::userPrepay($order->user_id)->first();
+                        (new OrderService($prepaidOrder))->activatePlan();
+                    } else {
+                        (new OrderService($order))->refreshAccountExpiration();
+                    }
+                    break;
+                case 1: // 待确认支付
+                    Notification::send(User::find(1), new PaymentConfirm($order));
+                    break;
+                case 2: // 支付成功
+                    if ($originalStatus !== 3) {
+                        (new OrderService($order))->receivedPayment();
+                    }
+                    break;
+                case 3: // 预支付订单
+                    if (Order::userActivePlan($order->user_id)->doesntExist()) {
+                        $prepaidOrder = Order::userPrepay($order->user_id)->first();
+                        if ($prepaidOrder) {
+                            (new OrderService($prepaidOrder))->activatePlan();
+                        }
+                    } else {
+                        (new OrderService($order))->refreshAccountExpiration();
+                    }
+                    break;
+            }
         }
     }
 

+ 47 - 29
app/Observers/UserObserver.php

@@ -24,43 +24,61 @@ class UserObserver
     public function updated(User $user): void
     {
         $changes = $user->getChanges();
-        $enableChange = Arr::has($changes, ['enable']);
-        if (($user->enable === 1 || $enableChange) && Arr::hasAny($changes, ['level', 'user_group_id', 'enable', 'port', 'passwd', 'speed_limit'])) {
-            $allowNodes = $user->nodes()->whereType(4)->get();
-            if (Arr::hasAny($changes, ['level', 'user_group_id'])) {
-                $oldAllowNodes = $user->nodes($user->getOriginal('level'), $user->getOriginal('user_group_id'))->whereType(4)->get();
-                if ($enableChange) {
-                    if ($user->enable === 0 && $oldAllowNodes->isNotEmpty()) {
-                        DelUser::dispatch($user->id, $oldAllowNodes);
-                    } elseif ($user->enable === 1 && $allowNodes->isNotEmpty()) {
-                        AddUser::dispatch($user->id, $allowNodes);
+        $enableChanged = Arr::has($changes, 'enable');
+        $permissionFieldsChanged = Arr::hasAny($changes, ['level', 'user_group_id']);
+        $configFieldsChanged = Arr::hasAny($changes, ['port', 'passwd', 'speed_limit']);
+
+        // 如果enable状态发生变化,或者用户当前已启用且权限或配置字段发生变化
+        if ($enableChanged || ($user->enable === 1 && ($permissionFieldsChanged || $configFieldsChanged))) {
+            // 获取当前允许的节点
+            $currentAllowedNodes = $user->nodes()->whereType(4)->get();
+
+            if ($permissionFieldsChanged) {
+                $oldAllowedNodes = $user->nodes($user->getOriginal('level'), $user->getOriginal('user_group_id'))->whereType(4)->get();
+                if ($enableChanged) {
+                    if ($user->enable) {
+                        // 用户被启用,添加到所有当前允许的节点
+                        if ($currentAllowedNodes->isNotEmpty()) {
+                            AddUser::dispatch($user->id, $currentAllowedNodes);
+                        }
+                    } elseif ($oldAllowedNodes->isNotEmpty()) {
+                        DelUser::dispatch($user->id, $oldAllowedNodes);
                     }
                 } else {
-                    $old = $oldAllowNodes->diff($allowNodes); // old 有 allow 没有
-                    $new = $allowNodes->diff($oldAllowNodes); // allow 有 old 没有
-                    if ($old->isNotEmpty()) {
-                        DelUser::dispatch($user->id, $old);
+                    // 计算差异
+                    $nodesToRemove = $oldAllowedNodes->diff($currentAllowedNodes); // 用户失去权限的节点
+                    $nodesToAdd = $currentAllowedNodes->diff($oldAllowedNodes); // 用户新增权限的节点
+
+                    // 处理节点移除
+                    if ($nodesToRemove->isNotEmpty()) {
+                        DelUser::dispatch($user->id, $nodesToRemove);
                     }
-                    if ($new->isNotEmpty()) {
-                        AddUser::dispatch($user->id, $new);
+
+                    // 处理节点添加
+                    if ($nodesToAdd->isNotEmpty()) {
+                        AddUser::dispatch($user->id, $nodesToAdd);
                     }
-                    if (Arr::hasAny($changes, ['port', 'passwd', 'speed_limit'])) {
-                        $same = $allowNodes->intersect($oldAllowNodes); // 共有部分
-                        if ($same->isNotEmpty()) {
-                            EditUser::dispatch($user, $same);
+
+                    // 处理节点更新(权限未变但配置变了)
+                    if ($configFieldsChanged && $currentAllowedNodes->isNotEmpty()) {
+                        $nodesToUpdate = $currentAllowedNodes->intersect($oldAllowedNodes); // 权限未变但可能需要更新配置的节点
+                        if ($nodesToUpdate->isNotEmpty()) {
+                            EditUser::dispatch($user, $nodesToUpdate);
                         }
                     }
                 }
-            } elseif ($allowNodes->isNotEmpty()) {
-                if ($enableChange) {
-                    if ($user->enable === 1) { // TODO: 由于vnet未正确使用enable字段,临时解决方案
-                        AddUser::dispatch($user->id, $allowNodes);
-                    } else {
-                        DelUser::dispatch($user->id, $allowNodes);
-                    }
-                } elseif (Arr::hasAny($changes, ['port', 'passwd', 'speed_limit'])) {
-                    EditUser::dispatch($user, $allowNodes);
+            } elseif ($enableChanged && $currentAllowedNodes->isNotEmpty()) {
+                // 启用状态变化处理
+                if ($user->enable) {
+                    // 用户被启用,添加到所有允许的节点
+                    AddUser::dispatch($user->id, $currentAllowedNodes);
+                } else {
+                    // 用户被禁用,从所有允许的节点中移除
+                    DelUser::dispatch($user->id, $currentAllowedNodes);
                 }
+            } elseif ($configFieldsChanged && $currentAllowedNodes->isNotEmpty()) {
+                // 仅配置变化,更新所有允许的节点
+                EditUser::dispatch($user, $currentAllowedNodes);
             }
         }
 

+ 28 - 35
app/Services/OrderService.php

@@ -12,27 +12,27 @@ use Log;
 
 class OrderService
 {
-    public static User $user;
+    private User $user;
 
-    public static ?Goods $goods;
+    private ?Goods $goods;
 
-    public static ?Payment $payment;
+    private ?Payment $payment;
 
     public function __construct(private readonly Order $order)
     { // 获取需要的信息
-        self::$user = $order->user;
-        self::$goods = $order->goods;
-        self::$payment = $order->payment;
+        $this->user = $order->user;
+        $this->goods = $order->goods;
+        $this->payment = $order->payment;
     }
 
     public function receivedPayment(): bool
     { // 支付成功后处理
-        $payment = self::$payment;
+        $payment = $this->payment;
         if ($payment && $payment->status !== 1) {// 是否为余额购买套餐
             $payment->complete();
         }
 
-        $goods = self::$goods;
+        $goods = $this->goods;
         if ($goods === null) {
             $ret = $this->chargeCredit();
         } else {
@@ -41,12 +41,12 @@ class OrderService
                     $ret = $this->activatePackage();
                     break;
                 case 2: // 套餐
-                    if (Order::userActivePlan(self::$user->id)->where('id', '<>', $this->order->id)->exists()) {// 判断套餐是否直接激活
+                    if (Order::userActivePlan($this->user->id)->where('id', '<>', $this->order->id)->exists()) {// 判断套餐是否直接激活
                         $ret = $this->order->prepay();
                     } else {
                         $ret = $this->activatePlan();
                     }
-                    $this->setCommissionExpense(self::$user); // 返利
+                    $this->setCommissionExpense($this->user); // 返利
                     break;
                 default:
                     Log::emergency('【处理订单】出现错误-未知套餐类型');
@@ -58,11 +58,11 @@ class OrderService
 
     private function chargeCredit(): bool
     { // 余额充值
-        $credit = self::$user->credit;
-        $ret = self::$user->updateCredit($this->order->origin_amount);
+        $credit = $this->user->credit;
+        $ret = $this->user->updateCredit($this->order->origin_amount);
         // 余额变动记录日志
         if ($ret) {
-            Helpers::addUserCreditLog($this->order->user_id, $this->order->id, $credit, self::$user->credit, $this->order->amount, 'The user topped up the balance.');
+            Helpers::addUserCreditLog($this->order->user_id, $this->order->id, $credit, $this->user->credit, $this->order->amount, 'The user topped up the balance.');
         }
 
         return $ret;
@@ -70,8 +70,8 @@ class OrderService
 
     private function activatePackage(): bool
     { // 激活流量包
-        if (self::$user->incrementData(self::$goods->traffic * MiB)) {
-            return Helpers::addUserTrafficModifyLog($this->order->user_id, self::$user->transfer_enable - self::$goods->traffic * MiB, self::$user->transfer_enable, trans("[:payment] plus the user's purchased data plan.", ['payment' => $this->order->pay_way]));
+        if ($this->user->incrementData($this->goods->traffic * MiB)) {
+            return Helpers::addUserTrafficModifyLog($this->order->user_id, $this->user->transfer_enable - $this->goods->traffic * MiB, $this->user->transfer_enable, trans("[:payment] plus the user's purchased data plan.", ['payment' => $this->order->pay_way]));
         }
 
         return false;
@@ -79,23 +79,23 @@ class OrderService
 
     public function activatePlan(): bool
     { // 激活套餐
-        $this->order->refresh()->update(['expired_at' => date('Y-m-d H:i:s', strtotime(self::$goods->days.' days'))]);
-        $oldData = self::$user->transfer_enable;
+        $this->order->refresh()->updateQuietly(['expired_at' => date('Y-m-d H:i:s', strtotime($this->goods->days.' days')), 'status' => 2]);
+        $oldData = $this->user->transfer_enable;
         $updateData = [
-            'invite_num' => self::$user->invite_num + (self::$goods->invite_num ?: 0),
-            'level' => self::$goods->level,
-            'speed_limit' => self::$goods->speed_limit,
+            'invite_num' => $this->user->invite_num + ($this->goods->invite_num ?: 0),
+            'level' => $this->goods->level,
+            'speed_limit' => $this->goods->speed_limit,
             'enable' => 1,
             ...$this->resetTimeAndData(),
         ];
 
         // 无端口用户 添加端口
-        if (empty(self::$user->port)) {
+        if (empty($this->user->port)) {
             $updateData['port'] = Helpers::getPort();
         }
 
-        if (self::$user->update($updateData)) {
-            return Helpers::addUserTrafficModifyLog($this->order->user_id, $oldData, self::$user->transfer_enable, trans("[:payment] plus the user's purchased data plan.", ['payment' => $this->order->pay_way]), $this->order->id);
+        if ($this->user->update($updateData)) {
+            return Helpers::addUserTrafficModifyLog($this->order->user_id, $oldData, $this->user->transfer_enable, trans("[:payment] plus the user's purchased data plan.", ['payment' => $this->order->pay_way]), $this->order->id);
         }
 
         return false;
@@ -108,7 +108,7 @@ class OrderService
         }
 
         // 账号流量重置日期
-        $nextResetTime = now()->addDays(self::$goods->period)->toDateString();
+        $nextResetTime = now()->addDays($this->goods->period)->toDateString();
         if ($nextResetTime >= $expired_at) {
             $nextResetTime = null;
         }
@@ -116,7 +116,7 @@ class OrderService
         return [
             'u' => 0,
             'd' => 0,
-            'transfer_enable' => self::$goods->traffic * MiB,
+            'transfer_enable' => $this->goods->traffic * MiB,
             'expired_at' => $expired_at,
             'reset_time' => $nextResetTime,
         ];
@@ -124,7 +124,7 @@ class OrderService
 
     private function getFinallyExpiredTime(): string
     { // 推算最新的到期时间
-        $orders = self::$user->orders()->whereIn('status', [2, 3])->whereIsExpire(0)->isPlan()->get();
+        $orders = $this->user->orders()->whereIn('status', [2, 3])->whereIsExpire(0)->isPlan()->get();
         $current = $orders->where('status', '==', 2)->first();
 
         return ($current->expired_at ?? now())->addDays($orders->except($current->id ?? 0)->sum('goods.days'))->toDateString();
@@ -160,7 +160,7 @@ class OrderService
     { // 刷新账号有效时间
         $data = ['expired_at' => $this->getFinallyExpiredTime()];
 
-        if ($data['expired_at'] < now()->toDateString()) {
+        if ($data['expired_at'] <= now()->toDateString()) {
             $data += [
                 'u' => 0,
                 'd' => 0,
@@ -172,13 +172,6 @@ class OrderService
             ];
         }
 
-        return self::$user->update($data);
-    }
-
-    public function activatePrepaidPlan(): bool
-    { // 激活预支付套餐
-        $this->order->complete();
-
-        return $this->activatePlan();
+        return $this->user->update($data);
     }
 }

+ 3 - 3
app/Utils/Helpers.php

@@ -34,12 +34,12 @@ class Helpers
      * @param  string  $username  用户名
      * @param  string  $password  用户密码
      * @param  int  $transfer_enable  可用流量
-     * @param  int  $date  可使用天数
+     * @param  int  $days  可使用天数
      * @param  int|null  $inviter_id  邀请人
      * @param  string|null  $nickname  昵称
      * @param  int  $status  状态:-1-禁用、0-未激活、1-正常
      */
-    public static function addUser(string $username, string $password, int $transfer_enable = 0, int $date = 0, ?int $inviter_id = null, ?string $nickname = null, int $status = 0): User
+    public static function addUser(string $username, string $password, int $transfer_enable = 0, int $days = 7, ?int $inviter_id = null, ?string $nickname = null, int $status = 0): User
     {
         return User::create([
             'nickname' => $nickname ?? $username,
@@ -52,7 +52,7 @@ class Helpers
             'protocol' => self::getDefaultProtocol(),
             'obfs' => self::getDefaultObfs(),
             'transfer_enable' => $transfer_enable,
-            'expired_at' => now()->addDays($date)->toDateString(),
+            'expired_at' => now()->addDays($days)->toDateString(),
             'user_group_id' => null,
             'reg_ip' => IP::getClientIp(),
             'inviter_id' => $inviter_id,

+ 3 - 1
composer.json

@@ -79,7 +79,9 @@
       "@php artisan package:discover --ansi"
     ],
     "post-update-cmd": [
-      "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
+      "@php artisan vendor:publish --tag=laravel-assets --ansi --force",
+      "if [ -f vendor/bin/ide-helper ]; then @php artisan ide-helper:generate; fi",
+      "if [ -f vendor/bin/ide-helper ]; then @php artisan ide-helper:meta; fi"
     ],
     "post-root-package-install": [
       "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""

+ 12 - 10
resources/views/user/invoices.blade.php

@@ -79,16 +79,18 @@
     <script src="/assets/global/vendor/bootstrap-table/bootstrap-table.min.js"></script>
     <script src="/assets/global/vendor/bootstrap-table/extensions/mobile/bootstrap-table-mobile.min.js"></script>
     <script>
-        function closePlan() {
-            showConfirm({
-                title: '{{ trans('user.invoice.active_prepaid_question') }}',
-                html: `{!! trans('user.invoice.active_prepaid_tips') !!}`,
-                icon: 'warning',
-                onConfirm: function() {
-                    ajaxPost('{{ route('invoice.activate') }}');
-                }
-            });
-        }
+        @if ($prepaidPlan)
+            function closePlan() {
+                showConfirm({
+                    title: '{{ trans('user.invoice.active_prepaid_question') }}',
+                    html: `{!! trans('user.invoice.active_prepaid_tips') !!}`,
+                    icon: 'warning',
+                    onConfirm: function() {
+                        ajaxPost('{{ route('invoice.activate') }}');
+                    }
+                });
+            }
+        @endif
 
         function closeOrder(id) {
             showConfirm({