trafficResetService = $trafficResetService; } /** * Execute the console command. */ public function handle(): int { $batchSize = (int) $this->option('batch-size'); $dryRun = $this->option('dry-run'); $maxTime = (int) $this->option('max-time'); $this->info('🚀 开始执行流量重置任务...'); $this->info("批次大小: {$batchSize} 用户/批"); $this->info("最大执行时间: {$maxTime} 秒"); if ($dryRun) { $this->warn('⚠️ 预演模式 - 不会实际执行重置操作'); } // 设置最大执行时间 set_time_limit($maxTime); try { if ($dryRun) { $result = $this->performDryRun($batchSize); } else { // 使用游标分页和进度回调 $result = $this->trafficResetService->batchCheckReset($batchSize, function ($progress) { $this->info("📦 处理第 {$progress['batch_number']} 批 ({$progress['batch_size']} 用户) - 已处理: {$progress['total_processed']}"); }); } $this->displayResults($result, $dryRun); return self::SUCCESS; } catch (\Exception $e) { $this->error("❌ 任务执行失败: {$e->getMessage()}"); Log::error('流量重置命令执行失败', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'options' => [ 'batch_size' => $batchSize, 'dry_run' => $dryRun, 'max_time' => $maxTime, ], ]); return self::FAILURE; } } /** * 显示执行结果 */ private function displayResults(array $result, bool $dryRun): void { $this->info("✅ 任务完成!"); $this->line(''); if ($dryRun) { $this->info("📊 预演结果统计:"); $this->info("📋 待处理用户数: {$result['total_found']}"); $this->info("⏱️ 预计处理时间: ~{$result['estimated_duration']} 秒"); $this->info("🗂️ 预计批次数: {$result['estimated_batches']}"); } else { $this->info("📊 执行结果统计:"); $this->info("👥 处理用户总数: {$result['total_processed']}"); $this->info("🔄 重置用户数量: {$result['total_reset']}"); $this->info("📦 处理批次数量: {$result['total_batches']}"); $this->info("⏱️ 总执行时间: {$result['duration']} 秒"); if ($result['error_count'] > 0) { $this->warn("⚠️ 错误数量: {$result['error_count']}"); $this->warn("详细错误信息请查看日志"); } else { $this->info("✨ 无错误发生"); } // 显示性能指标 if ($result['total_processed'] > 0) { $avgTime = round($result['duration'] / $result['total_processed'], 4); $this->info("⚡ 平均处理速度: {$avgTime} 秒/用户"); } } } /** * 执行预演模式 */ private function performDryRun(int $batchSize): array { $this->info("🔍 扫描需要重置的用户..."); $totalUsers = User::where('next_reset_at', '<=', time()) ->whereNotNull('next_reset_at') ->where(function ($query) { $query->where('expired_at', '>', time()) ->orWhereNull('expired_at'); }) ->where('banned', 0) ->whereNotNull('plan_id') ->count(); if ($totalUsers === 0) { $this->info("😴 当前没有需要重置的用户"); return [ 'total_found' => 0, 'estimated_duration' => 0, 'estimated_batches' => 0, ]; } $this->info("找到 {$totalUsers} 个需要重置的用户"); // 预计批次数 $estimatedBatches = ceil($totalUsers / $batchSize); // 预计执行时间(基于经验值:每个用户平均0.1秒) $estimatedDuration = round($totalUsers * 0.1, 1); $this->info("将分 {$estimatedBatches} 个批次处理(每批 {$batchSize} 用户)"); // 显示前几个用户的详情作为示例 if ($this->option('verbose') || $totalUsers <= 20) { $sampleUsers = User::where('next_reset_at', '<=', time()) ->whereNotNull('next_reset_at') ->where(function ($query) { $query->where('expired_at', '>', time()) ->orWhereNull('expired_at'); }) ->where('banned', 0) ->whereNotNull('plan_id') ->with('plan') ->limit(min(20, $totalUsers)) ->get(); $table = []; foreach ($sampleUsers as $user) { $table[] = [ 'ID' => $user->id, '邮箱' => substr($user->email, 0, 20) . (strlen($user->email) > 20 ? '...' : ''), '套餐' => $user->plan->name ?? 'N/A', '下次重置' => Carbon::createFromTimestamp($user->next_reset_at)->format('Y-m-d H:i:s'), '当前流量' => Helper::trafficConvert(($user->u ?? 0) + ($user->d ?? 0)), '重置次数' => $user->reset_count, ]; } if (!empty($table)) { $this->info("📋 示例用户列表" . ($totalUsers > 20 ? "(显示前20个):" : ":")); $this->table([ 'ID', '邮箱', '套餐', '下次重置', '当前流量', '重置次数' ], $table); } } return [ 'total_found' => $totalUsers, 'estimated_duration' => $estimatedDuration, 'estimated_batches' => $estimatedBatches, ]; } }