alroyso 1 tháng trước cách đây
mục cha
commit
da51333b39

+ 18 - 5
.idea/workspace.xml

@@ -6,10 +6,13 @@
   <component name="ChangeListManager">
     <list default="true" id="09451f28-815a-407f-8951-727d305b50a4" name="Changes" comment="Changes">
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/app/Console/Commands/AutoJob.php" beforeDir="false" afterPath="$PROJECT_DIR$/app/Console/Commands/AutoJob.php" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/app/Console/Commands/AutoStatisticsUserDailyTraffic.php" beforeDir="false" afterPath="$PROJECT_DIR$/app/Console/Commands/AutoStatisticsUserDailyTraffic.php" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/app/Console/Commands/AutoStatisticsUserHourlyTraffic.php" beforeDir="false" afterPath="$PROJECT_DIR$/app/Console/Commands/AutoStatisticsUserHourlyTraffic.php" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/app/Console/Commands/UserTrafficAbnormalAutoWarning.php" beforeDir="false" afterPath="$PROJECT_DIR$/app/Console/Commands/UserTrafficAbnormalAutoWarning.php" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/app/Console/Kernel.php" beforeDir="false" afterPath="$PROJECT_DIR$/app/Console/Kernel.php" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/app/Http/Controllers/Api/Client/V3Controller.php" beforeDir="false" afterPath="$PROJECT_DIR$/app/Http/Controllers/Api/Client/V3Controller.php" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/app/Http/Controllers/Api/Client/V4Controller.php" beforeDir="false" afterPath="$PROJECT_DIR$/app/Http/Controllers/Api/Client/V4Controller.php" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/resources/lang/zh-CN/validation.php" beforeDir="false" afterPath="$PROJECT_DIR$/resources/lang/zh-CN/validation.php" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/routes/api.php" beforeDir="false" afterPath="$PROJECT_DIR$/routes/api.php" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/app/Models/UserDailyDataFlow.php" beforeDir="false" afterPath="$PROJECT_DIR$/app/Models/UserDailyDataFlow.php" afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -383,7 +386,9 @@
       <workItem from="1741836315642" duration="9903000" />
       <workItem from="1741924782890" duration="4397000" />
       <workItem from="1742356565477" duration="2737000" />
-      <workItem from="1742627488124" duration="1451000" />
+      <workItem from="1742627488124" duration="1465000" />
+      <workItem from="1745205587369" duration="5133000" />
+      <workItem from="1746161383498" duration="16222000" />
     </task>
     <task id="LOCAL-00001" summary="Changes">
       <option name="closed" value="true" />
@@ -681,7 +686,15 @@
       <option name="project" value="LOCAL" />
       <updated>1741851831310</updated>
     </task>
-    <option name="localTasksCounter" value="38" />
+    <task id="LOCAL-00038" summary="Changes">
+      <option name="closed" value="true" />
+      <created>1742633122643</created>
+      <option name="number" value="00038" />
+      <option name="presentableId" value="LOCAL-00038" />
+      <option name="project" value="LOCAL" />
+      <updated>1742633122643</updated>
+    </task>
+    <option name="localTasksCounter" value="39" />
     <servers />
   </component>
   <component name="TypeScriptGeneratedFilesManager">

+ 122 - 117
app/Console/Commands/AutoJob.php

@@ -10,6 +10,7 @@ use App\Models\Node;
 use App\Models\Order;
 use App\Models\User;
 use App\Models\VerifyCode;
+use App\Models\UserHourlyDataFlow;
 use Illuminate\Console\Command;
 use Log;
 
@@ -18,163 +19,167 @@ class AutoJob extends Command
     protected $signature = 'autoJob';
     protected $description = '自动化任务';
 
-    /*
-     * 警告:除非熟悉业务流程,否则不推荐更改以下执行顺序,随意变更以下顺序可能导致系统异常
-     */
     public function handle(): void
     {
         $jobStartTime = microtime(true);
 
-//        // 关闭超时未支付本地订单
-          Order::query()->recentUnPay()->update(['status' => -1]);
-//
-//        //过期验证码、优惠券、邀请码无效化
-         //$this->expireCode();
-//
-//        // 封禁访问异常的订阅链接
-//        $this->blockSubscribe();
-//
-//        // 封禁账号
+        // 关闭超时未支付本地订单
+       // Order::query()->recentUnPay()->update(['status' => -1]);
+        Log::info('---开启实执行自动化任务');
+        // 封禁账号
         $this->blockUsers();
-//
-//        // 解封被封禁的账号
+        Log::info('---开启实执行封禁账号');
+        // 解封被封禁的账号
         $this->unblockUsers();
-//
-//       // $this->checkNodeWeihu();
-//
-//        // 端口回收与分配
+        Log::info('---开启实执行解封被封禁的账号');
+        // 端口回收与分配
         if (sysConfig('auto_release_port')) {
             $this->dispatchPort();
+            Log::info('---开启实执行端口回收与分配');
         }
-//
-//        // 检查维护模式
-//        if (sysConfig('maintenance_mode') && sysConfig('maintenance_time') && sysConfig('maintenance_time') <= date('c')) {
-//            Config::whereIn('name', ['maintenance_mode', 'maintenance_content', 'maintenance_time'])->update(['value' => null]);
-//        }
-//
+
         $jobEndTime = microtime(true);
         $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
 
         Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
     }
 
-    // 注册验证码自动置无效 & 优惠券无效化
-    private function expireCode(): void
-    {
-        // 注册验证码自动置无效
-        VerifyCode::recentUnused()->update(['status' => 2]);
-
-        // 优惠券到期 / 用尽的 自动置无效
-        Coupon::withTrashed()->whereStatus(0)->where('end_time', '<=', time())->orWhereIn('type', [1, 2])->whereUsableTimes(0)->update(['status' => 2]);
-
-        // 邀请码到期自动置无效
-        Invite::whereStatus(0)->where('dateline', '<=', date('Y-m-d H:i:s'))->update(['status' => 2]);
-    }
-
-    // 封禁访问异常的订阅链接
-    private function blockSubscribe(): void
-    {
-        if (sysConfig('is_subscribe_ban')) {
-            $subscribe_ban_times = sysConfig('subscribe_ban_times');
-            foreach (User::activeUser()->with('subscribe')->get() as $user) {
-                if (! $user->subscribe || $user->subscribe->status === 0) { // 无订阅链接 或 已封
-                    continue;
-                }
-                // 24小时内不同IP的请求次数
-                $request_times = $user->subscribeLogs()
-                    ->where('request_time', '>=', date('Y-m-d H:i:s', strtotime('-1 days')))
-                    ->distinct()
-                    ->count('request_ip');
-                if ($request_times >= $subscribe_ban_times) {
-                    $user->subscribe->update([
-                        'status'   => 0,
-                        'ban_time' => strtotime('+'.sysConfig('traffic_ban_time').' minutes'),
-                        'ban_desc' => '存在异常,自动封禁',
-                    ]);
-
-                    // 记录封禁日志
-                    Helpers::addUserBanLog($user->id, 0, '【完全封禁订阅】-订阅24小时内请求异常');
-                }
-            }
-        }
-    }
-
-    // 封禁账号
     private function blockUsers(): void
     {
         // 禁用流量超限用户
-        foreach (User::activeUser()->whereRaw('u + d >= transfer_enable')->get() as $user) {
-            $user->update(['enable' => 0]);
-
-            // 写入日志
-            Helpers::addUserBanLog($user->id, 0, '【封禁代理】-流量已用完');
-        }
+        User::activeUser()
+            ->whereRaw('u + d >= transfer_enable')
+            ->chunk(100, function ($users) {
+                foreach ($users as $user) {
+                    $user->update(['enable' => 0]);
+                    Helpers::addUserBanLog($user->id, 0, '【封禁代理】-流量已用完');
+                    \Log::info('用户流量超限被封禁', [
+                        'user_id' => $user->id,
+                        'email' => $user->email,
+                        'used_traffic' => $user->u + $user->d,
+                        'transfer_enable' => $user->transfer_enable,
+                        'ban_time' => now()
+                    ]);
+                }
+            });
 
         // 封禁1小时内流量异常账号
         if (sysConfig('is_traffic_ban')) {
             $trafficBanTime = sysConfig('traffic_ban_time');
-            foreach (User::activeUser()->whereBanTime(null)->get() as $user) {
-                // 多往前取5分钟,防止数据统计任务执行时间过长导致没有数据
-                if ($user->id  == 1684){
-                    continue;
-                }
-
-                if ($user->isTrafficWarning()) {
-                    $user->update([
-                        'enable'   => 0,
-                        'ban_time' => strtotime('+'.$trafficBanTime.' minutes'),
-                    ]);
-
-                    // 写入日志
-                    Helpers::addUserBanLog($user->id, $trafficBanTime, '【临时封禁代理】-1小时内流量异常');
-                }
+            $trafficBanValue = sysConfig('traffic_ban_value') * GB;
+
+            // 获取最近1小时流量异常的用户ID列表
+            $abnormalUserIds = UserHourlyDataFlow::where('created_at', '>=', now()->subHour())
+                ->where('total', '>', $trafficBanValue)
+                ->distinct()
+                ->pluck('user_id')
+                ->toArray();
+
+            if (!empty($abnormalUserIds)) {
+                \Log::info('发现流量异常用户', [
+                    'count' => count($abnormalUserIds),
+                    'user_ids' => $abnormalUserIds
+                ]);
+
+                // 直接查询这些异常用户并封禁
+                User::activeUser()
+                    ->whereIn('id', $abnormalUserIds)
+                    ->whereBanTime(null)
+                    ->chunk(100, function ($users) use ($trafficBanTime, $trafficBanValue) {
+                        foreach ($users as $user) {
+                            try {
+                                \Log::warning('即将封禁流量异常用户', [
+                                    'user_id' => $user->id,
+                                    'email' => $user->email,
+                                    'before_enable' => $user->enable,
+                                    'before_ban_time' => $user->ban_time,
+                                    'traffic_ban_value' => $trafficBanValue
+                                ]);
+
+                                $user->update([
+                                    'enable' => 0,
+                                    'ban_time' => strtotime("+$trafficBanTime minutes")
+                                ]);
+
+                                $user->refresh();
+                                \Log::info('流量异常用户封禁完成', [
+                                    'user_id' => $user->id,
+                                    'after_enable' => $user->enable,
+                                    'after_ban_time' => $user->ban_time,
+                                    'ban_minutes' => $trafficBanTime
+                                ]);
+                                $total = $user->usedTraffic();
+                                $banValue = $trafficBanValue;
+                                $banValueStr = flowAutoShow($banValue);
+                                $usedStr = flowAutoShow($total);
+                                \Log::warning("⚠️ 用户流量异常封禁 | ID: {$user->id} | 邮箱: {$user->email} | 1小时内已用流量: {$usedStr},阈值: {$banValueStr}(已超限)");
+                                Helpers::addUserBanLog($user->id, $trafficBanTime, '【临时封禁代理】-1小时内流量异常');
+                            } catch (\Exception $e) {
+                                \Log::error('封禁流量异常用户失败', [
+                                    'user_id' => $user->id,
+                                    'error' => $e->getMessage()
+                                ]);
+                            }
+                        }
+                    });
+            } else {
+                \Log::info('未发现流量异常用户');
             }
         }
     }
 
-    // 解封被临时封禁的账号
     private function unblockUsers(): void
     {
         // 解封被临时封禁的账号
-        $userList = User::whereEnable(0)->where('status', '>=', 0)->whereNotNull('ban_time')->where('ban_time', '<', time())->get();
-        foreach ($userList as $user) {
-            $user->update(['enable' => 1, 'ban_time' => null]);
-
-            // 写入操作日志
-            Helpers::addUserBanLog($user->id, 0, '【自动解封】-临时封禁到期');
-        }
+        User::whereEnable(0)
+            ->where('status', '>=', 0)
+            ->whereNotNull('ban_time')
+            ->where('ban_time', '<', time())
+            ->chunk(100, function ($users) {
+                foreach ($users as $user) {
+                    $user->update(['enable' => 1, 'ban_time' => null]);
+                    Helpers::addUserBanLog($user->id, 0, '【自动解封】-临时封禁到期');
+                    \Log::info('用户临时封禁到期自动解封', [
+                        'user_id' => $user->id,
+                        'email' => $user->email,
+                        'ban_time' => $user->ban_time,
+                        'unblock_time' => now(),
+                        'reason' => '临时封禁到期'
+                    ]);
+                }
+            });
 
-        // 可用流量大于已用流量也解封(比如:邀请返利自动加了流量)
-        $userList = User::whereEnable(0)
+        // 解封流量恢复的账号
+        User::whereEnable(0)
             ->where('status', '>=', 0)
             ->whereBanTime(null)
             ->where('expired_at', '>=', date('Y-m-d H:i:s'))
             ->whereRaw('u + d < transfer_enable')
-            ->get();
-
-        //Log::info('查询数据封禁的账号--------:'.var_export($userList,true));
-
-        foreach ($userList as $user) {
-            $user->update(['enable' => 1]);
-
-            // 写入操作日志
-            Helpers::addUserBanLog($user->id, 0, '【自动解封】-有流量解封');
-        }
+            ->chunk(100, function ($users) {
+                foreach ($users as $user) {
+                    $user->update(['enable' => 1]);
+                    Helpers::addUserBanLog($user->id, 0, '【自动解封】-有流量解封');
+                    \Log::info('用户流量恢复自动解封', [
+                        'user_id' => $user->id,
+                        'email' => $user->email,
+                        'used_traffic' => $user->u + $user->d,
+                        'transfer_enable' => $user->transfer_enable,
+                        'expired_at' => $user->expired_at,
+                        'unblock_time' => now(),
+                        'reason' => '流量恢复'
+                    ]);
+                }
+            });
     }
 
-    // 端口回收与分配
     private function dispatchPort(): void
     {
-        // 自动分配端口
-//        User::activeUser()->wherePort(0)->get()->each(function ($user) {
-//            $user->update(['port' => Helpers::getPort()]);
-//        });
-
         // 被封禁 / 过期一个月 的账号自动释放端口
         User::where('port', '<>', 0)
-            ->whereStatus(-1)
-            ->orWhere('expired_at', '<=', date('Y-m-d H:i:s', strtotime('-1 months')))
+            ->where(function ($query) {
+                $query->where('status', -1)
+                    ->orWhere('expired_at', '<=', date('Y-m-d H:i:s', strtotime('-1 months')));
+            })
             ->update(['port' => 0]);
     }
-
 }

+ 67 - 33
app/Console/Commands/AutoStatisticsUserDailyTraffic.php

@@ -17,44 +17,78 @@ class AutoStatisticsUserDailyTraffic extends Command
     public function handle(): void
     {
         $jobStartTime = microtime(true);
+        $today = date('Y-m-d');
+        $startTime = strtotime($today);
+        $endTime = time();
 
-        foreach (User::activeUser()->get() as $user) {
-            // 统计一次所有节点的总和
-            $this->statisticsByUser($user->id);
+        try {
+            // 缓存用户和节点存在性检查
+            $checkedUsers = [];
+            $checkedNodes = [];
+            
+            // 直接遍历 UserDataFlowLog 表
+            UserDataFlowLog::whereBetween('log_time', [$startTime, $endTime])
+                ->select('user_id', 'node_id')
+                ->selectRaw('SUM(u) as total_u, SUM(d) as total_d')
+                ->groupBy('user_id', 'node_id')
+                ->chunk(100, function ($logs) use ($checkedUsers, $checkedNodes, $today) {
+                    foreach ($logs as $log) {
+                        try {
+                            // 检查用户是否存在
+                            if (!isset($checkedUsers[$log->user_id])) {
+                                $checkedUsers[$log->user_id] = User::where('id', $log->user_id)->exists();
+                            }
+                            if (!$checkedUsers[$log->user_id]) {
+                                Log::warning("跳过记录,用户不存在: user_id={$log->user_id}");
+                                continue;
+                            }
 
-            // 统计每个节点产生的流量
-            foreach (Node::whereStatus(1)->orderBy('id')->get() as $node) {
-                $this->statisticsByUser($user->id, $node->id);
-            }
-        }
-
-        $jobEndTime = microtime(true);
-        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-        Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-    }
+                            // 检查节点是否存在(如果node_id不为null)
+                            if ($log->node_id !== null) {
+                                if (!isset($checkedNodes[$log->node_id])) {
+                                    $checkedNodes[$log->node_id] = Node::where('id', $log->node_id)->exists();
+                                }
+                                if (!$checkedNodes[$log->node_id]) {
+                                    Log::warning("跳过记录,节点不存在: node_id={$log->node_id}");
+                                    continue;
+                                }
+                            }
 
-    private function statisticsByUser($user_id, $node_id = null): void
-    {
-        $query = UserDataFlowLog::whereUserId($user_id)->whereBetween('log_time', [strtotime(date('Y-m-d')), time()]);
+                            $total = $log->total_u + $log->total_d;
+                            if ($total > 0) {
+                                UserDailyDataFlow::updateOrCreate(
+                                    [
+                                        'user_id' => $log->user_id,
+                                        'node_id' => $log->node_id,
+                                        'created_at' => $today
+                                    ],
+                                    [
+                                        'u' => $log->total_u,
+                                        'd' => $log->total_d,
+                                        'total' => $total,
+                                        'traffic' => flowAutoShow($total)
+                                    ]
+                                );
+                            }
+                        } catch (\Exception $e) {
+                            Log::error('处理流量记录失败', [
+                                'user_id' => $log->user_id,
+                                'node_id' => $log->node_id,
+                                'error' => $e->getMessage()
+                            ]);
+                        }
+                    }
+                });
 
-        if ($node_id) {
-            $query->whereNodeId($node_id);
-        }
+            $jobEndTime = microtime(true);
+            $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
 
-        $u = $query->sum('u');
-        $d = $query->sum('d');
-        $total = $u + $d;
-
-        if ($total) { // 有数据才记录
-            $obj = new UserDailyDataFlow();
-            $obj->user_id = $user_id;
-            $obj->node_id = $node_id;
-            $obj->u = $u;
-            $obj->d = $d;
-            $obj->total = $total;
-            $obj->traffic = flowAutoShow($total);
-            $obj->save();
+            Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
+        } catch (\Exception $e) {
+            Log::error('统计用户流量任务失败', [
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
         }
     }
 }

+ 99 - 21
app/Console/Commands/AutoStatisticsUserHourlyTraffic.php

@@ -14,47 +14,125 @@ class AutoStatisticsUserHourlyTraffic extends Command
     protected $signature = 'autoStatisticsUserHourlyTraffic';
     protected $description = '自动统计用户每小时流量';
 
+
+
     public function handle(): void
     {
         $jobStartTime = microtime(true);
+        echo "🟢 开始执行 autoStatisticsUserHourlyTraffic...\n";
+        Log::info('🟢 开始执行 autoStatisticsUserHourlyTraffic');
+
+        $startTime = strtotime('-1 hour');
+        $endTime = time();
+
+        // 获取 [user_id, node_id] 的流量聚合
+        $records = UserDataFlowLog::whereBetween('log_time', [$startTime, $endTime])
+            ->selectRaw('user_id, node_id, SUM(u) as sum_u, SUM(d) as sum_d')
+            ->groupBy('user_id', 'node_id')
+            ->get();
+
+        $userTotals = [];
+        $checkedUsers = [];
+        $checkedNodes = [];
+
+        foreach ($records as $record) {
+            $user_id = $record->user_id;
+            $node_id = $record->node_id;
+
+            // 缓存判断用户是否存在
+            if (!isset($checkedUsers[$user_id])) {
+                $checkedUsers[$user_id] = User::where('id', $user_id)->exists();
+            }
+            if (!$checkedUsers[$user_id]) {
+                Log::warning("跳过记录,用户不存在: user_id=$user_id");
+                continue;
+            }
+
+            // 缓存判断节点是否存在
+            if (!isset($checkedNodes[$node_id])) {
+                $checkedNodes[$node_id] = Node::where('id', $node_id)->exists();
+            }
+            if (!$checkedNodes[$node_id]) {
+                Log::warning("跳过记录,节点不存在: node_id=$node_id");
+                continue;
+            }
+
+            $u = $record->sum_u ?? 0;
+            $d = $record->sum_d ?? 0;
+            $total = $u + $d;
+
+            //Log::info("📊 保存流量记录 - user: $user_id, node: $node_id, u: $u, d: $d, total: $total");
 
-        foreach (User::activeUser()->get() as $user) {
-            // 统计一次所有节点的总和
-            $this->statisticsByNode($user->id);
+            UserHourlyDataFlow::create([
+                'user_id' => $user_id,
+                'node_id' => $node_id,
+                'u'       => $u,
+                'd'       => $d,
+                'total'   => $total,
+                'traffic' => flowAutoShow($total),
+            ]);
 
-            // 统计每个节点产生的流量
-            foreach (Node::whereStatus(1)->orderBy('id')->get() as $node) {
-                $this->statisticsByNode($user->id, $node->id);
+            // 累计每个用户的总流量
+            if (!isset($userTotals[$user_id])) {
+                $userTotals[$user_id] = 0;
             }
+            $userTotals[$user_id] += $total;
         }
 
-        $jobEndTime = microtime(true);
-        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+        // 判断是否超出阈值
+        $trafficBanValue = sysConfig('traffic_ban_value');
+
+        foreach ($userTotals as $user_id => $total) {
+            if ($total > $trafficBanValue * GB) {
+                $user = User::find($user_id);
+                $email = $user->email ?? '未知邮箱';
+                Log::warning("⚠️ 用户 [ID: $user_id, 邮箱: $email] 最近1小时流量异常:已使用 ".flowAutoShow($total));
+                echo "⚠️ 异常用户: $email, 使用 ".flowAutoShow($total)."\n";
+            }
+        }
 
-        Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
+        $jobUsedTime = round((microtime(true) - $jobStartTime), 4);
+        Log::info("✅ autoStatisticsUserHourlyTraffic 执行完成,耗时 {$jobUsedTime} 秒");
+        echo "✅ 执行完成,耗时 {$jobUsedTime} 秒\n";
     }
 
+
     private function statisticsByNode($user_id, $node_id = null): void
     {
-        $query = UserDataFlowLog::whereUserId($user_id)->whereBetween('log_time', [strtotime('-1 hour'), time()]);
+        $query = UserDataFlowLog::whereUserId($user_id)
+            ->whereBetween('log_time', [strtotime('-1 hour'), time()]);
 
         if ($node_id) {
             $query->whereNodeId($node_id);
         }
 
-        $u = $query->sum('u');
-        $d = $query->sum('d');
+        $sums = $query->selectRaw('SUM(u) as sum_u, SUM(d) as sum_d')->first();
+
+        $u = $sums->sum_u ?? 0;
+        $d = $sums->sum_d ?? 0;
         $total = $u + $d;
 
-        if ($total) { // 有数据才记录
-            $obj = new UserHourlyDataFlow();
-            $obj->user_id = $user_id;
-            $obj->node_id = $node_id;
-            $obj->u = $u;
-            $obj->d = $d;
-            $obj->total = $total;
-            $obj->traffic = flowAutoShow($total);
-            $obj->save();
+        if ($total > 0) {
+            Log::info("📊 保存流量记录 - user: $user_id, node: " . ($node_id ?? '总计') . ", u: $u, d: $d, total: $total");
+            UserHourlyDataFlow::create([
+                'user_id' => $user_id,
+                'node_id' => $node_id,
+                'u'       => $u,
+                'd'       => $d,
+                'total'   => $total,
+                'traffic' => flowAutoShow($total),
+            ]);
+        }
+
+        // 检查是否超过阈值(仅统计总流量)
+        if (is_null($node_id)) {
+            $trafficBanValue = sysConfig('traffic_ban_value');
+            if ($total > $trafficBanValue * GB) {
+                $user = User::find($user_id);
+                $email = $user->email ?? '未知邮箱';
+                Log::warning("⚠️ 用户 [ID: $user_id, 邮箱: $email] 最近1小时流量异常:已使用 ".flowAutoShow($total));
+                echo "⚠️ 异常用户: $email, 使用 ".flowAutoShow($total)."\n";
+            }
         }
     }
 }

+ 48 - 18
app/Console/Commands/UserTrafficAbnormalAutoWarning.php

@@ -32,27 +32,57 @@ class UserTrafficAbnormalAutoWarning extends Command
     // 用户流量异常警告
     private function userTrafficAbnormalWarning(): void
     {
-        // 1小时内流量异常用户(多往前取5分钟,防止数据统计任务执行时间过长导致没有数据)
-        $userTotalTrafficLogs = UserHourlyDataFlow::whereNodeId(null)
-            ->where('total', '>', MB * 50)
+
+        Log::info('---开始执行用户流量异常检测--');
+        $trafficBanValue = sysConfig('traffic_ban_value');
+        UserHourlyDataFlow::where('total', '>', MB * 50)
             ->where('created_at', '>=', date('Y-m-d H:i:s', time() - 3900))
+            ->whereNull('node_id')
             ->groupBy('user_id')
             ->selectRaw('user_id, sum(total) as totalTraffic')
-            ->get(); // 只统计100M以上的记录,加快查询速度
-        $trafficBanValue = sysConfig('traffic_ban_value');
+            ->chunk(100, function ($logs) use ($trafficBanValue) {
+                foreach ($logs as $log) {
+                    $user = $log->user;
+                    if (!$user) continue;
 
-        foreach ($userTotalTrafficLogs->load('user') as $log) {
-            // 推送通知管理员
-            if ($log->totalTraffic > $trafficBanValue * GB) {
-                $user = $log->user;
-                $traffic = UserHourlyDataFlow::userRecentUsed($user->id)
-                    ->selectRaw('user_id, sum(`u`) as totalU, sum(`d`) as totalD, sum(total) as totalTraffic')
-                    ->first();
-
-                Notification::send(User::permission('admin.user.edit,update')->orWhere(function ($query) {
-                    return $query->role('Super Admin');
-                })->get(), new DataAnomaly($user->id, flowAutoShow($traffic->totalU), flowAutoShow($traffic->totalD), flowAutoShow($traffic->totalTraffic)));
-            }
-        }
+                    if ($log->totalTraffic > $trafficBanValue * GB) {
+                        $traffic = UserHourlyDataFlow::userRecentUsed($user->id)
+                            ->selectRaw('user_id, sum(`u`) as totalU, sum(`d`) as totalD, sum(total) as totalTraffic')
+                            ->first();
+
+                        Notification::send(User::permission('admin.user.edit,update')->orWhere(function ($query) {
+                            return $query->role('Super Admin');
+                        })->get(), new DataAnomaly(
+                            $user->id,
+                            flowAutoShow($traffic->totalU),
+                            flowAutoShow($traffic->totalD),
+                            flowAutoShow($traffic->totalTraffic)
+                        ));
+                    }
+                }
+            });
+        Log::info('---开始执行用户流量异常检测完成--');
+//        // 1小时内流量异常用户(多往前取5分钟,防止数据统计任务执行时间过长导致没有数据)
+//        $userTotalTrafficLogs = UserHourlyDataFlow::whereNodeId(null)
+//            ->where('total', '>', MB * 50)
+//            ->where('created_at', '>=', date('Y-m-d H:i:s', time() - 3900))
+//            ->groupBy('user_id')
+//            ->selectRaw('user_id, sum(total) as totalTraffic')
+//            ->get(); // 只统计100M以上的记录,加快查询速度
+//        $trafficBanValue = sysConfig('traffic_ban_value');
+//
+//        foreach ($userTotalTrafficLogs->load('user') as $log) {
+//            // 推送通知管理员
+//            if ($log->totalTraffic > $trafficBanValue * GB) {
+//                $user = $log->user;
+//                $traffic = UserHourlyDataFlow::userRecentUsed($user->id)
+//                    ->selectRaw('user_id, sum(`u`) as totalU, sum(`d`) as totalD, sum(total) as totalTraffic')
+//                    ->first();
+//
+//                Notification::send(User::permission('admin.user.edit,update')->orWhere(function ($query) {
+//                    return $query->role('Super Admin');
+//                })->get(), new DataAnomaly($user->id, flowAutoShow($traffic->totalU), flowAutoShow($traffic->totalD), flowAutoShow($traffic->totalTraffic)));
+//            }
+//        }
     }
 }

+ 1 - 0
app/Console/Kernel.php

@@ -56,6 +56,7 @@ class Kernel extends ConsoleKernel
         $schedule->command('autoClearLog')->everyThirtyMinutes();
 //        $schedule->command('nodeStatusDetection')->everyTenMinutes();
         $schedule->command('autoStatisticsNodeHourlyTraffic')->hourly();
+        //$schedule->command('autoStatisticsUserHourlyTraffic')->hourly();
         $schedule->command('autoStatisticsUserHourlyTraffic')->hourly();
         $schedule->command('userTrafficAbnormalAutoWarning')->hourly();
         $schedule->command('dailyJob')->everyFifteenMinutes();

+ 13 - 8
app/Http/Controllers/Api/Client/V3Controller.php

@@ -78,13 +78,18 @@ class V3Controller  extends Controller
     //登陆
     public function login(Request $request)
     {
-        $validator = Validator::make($request->all(), [
-            'email' => 'required|email',
-            'password' => 'required|string|min:6',
-        ]);
-        if ($validator->fails()) {
-            return response()->json(['ret' => 0, 'msg' => $validator->errors()->all()], 200);
-        }
+            $validator = Validator::make($request->all(), [
+                'email' => 'required|email',
+                'password' => 'required|string|min:6',
+            ]);
+            if ($validator->fails()) {
+                if ($validator->fails()) {
+                    return response()->json([
+                        'ret' => 0,
+                        'msg' => implode(', ', $validator->errors()->all())
+                    ], 200);
+                }
+            }
 
         if ($token = auth()->attempt($validator->validated())) {
             return $this->createNewToken($request,$token);
@@ -135,7 +140,7 @@ class V3Controller  extends Controller
     public function getsysconfig(Request $request){
 
         //$url = "user.viptwo.xyz";
-        $url = "zf.payviptwo.top";
+        $url = "user.vip666999.xyz";
 
         $tag = $request->input('email');
         if (!isset($tag)){

+ 9 - 0
app/Models/UserDailyDataFlow.php

@@ -35,6 +35,15 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
 class UserDailyDataFlow extends Model
 {
     public const UPDATED_AT = null;
+    protected $fillable = [
+        'user_id',
+        'node_id',
+        'u',
+        'd',
+        'total',
+        'traffic',
+        'created_at'
+    ];
     protected $table = 'user_daily_data_flow';
 
     public function user(): BelongsTo