argument('identifier'); $type = $this->option('type'); $timeout = (int) $this->option('timeout'); $noclearLogs = $this->option('no-clear-logs'); // 如果开启了清空日志选项,则清空日志文件 if ($noclearLogs) { }else{ $this->clearLogFiles(); } $this->info("开始查找请求记录..."); $this->info("标识符: {$identifier}"); $this->info("类型: {$type}"); // 查找请求记录 $requestLog = $this->findRequestLog($identifier, $type); if (!$requestLog) { $this->error("未找到匹配的请求记录"); return 1; } $this->info("找到请求记录:"); $this->line(" ID: {$requestLog->id}"); $this->line(" Request UNID: {$requestLog->request_unid}"); $this->line(" Run UNID: {$requestLog->run_unid}"); $this->line(" UserId : {$requestLog->user_id}"); $this->line(" 路径: {$requestLog->path}"); $this->line(" 方法: {$requestLog->method}"); $this->line(" 创建时间: {$requestLog->created_at}"); // 检查必要的数据 if (empty($requestLog->protobuf_json)) { $this->error("请求记录中缺少 protobuf_json 数据"); return 1; } $this->info("请求数据"); dump(json_decode($requestLog->protobuf_json,true)); // 解析 headers 获取 token $token = $this->extractToken($requestLog->headers); if (!$token) { $this->warn("未找到 token,将不携带 token 发起请求"); } else { $this->info("提取到 token: " . substr($token, 0, 10) . "..."); } // user_id if($requestLog->user_id){ SessionHelper::sessionLogin($token,$requestLog->user_id); $this->info("token 设置用户: {$requestLog->user_id} "); } // 初始化 HTTP 客户端 $this->initializeHttpClient($timeout); // 发起请求 $this->info("开始发起请求..."); $response = $this->makeRequest($requestLog->protobuf_json, $token); if ($response === null) { return 1; } // 输出结果 // $this->info("请求完成,响应结果:"); // $this->line("状态码: " . $response['status_code']); // $this->line("响应头:"); // foreach ($response['headers'] as $name => $values) { // $this->line(" {$name}: " . implode(', ', $values)); // } $this->line("响应内容:"); dump(json_decode($response['body'],true)); return 0; } /** * 查找请求记录 * * @param string $identifier 标识符 * @param string $type 类型 * @return RequestLog|null */ protected function findRequestLog(string $identifier, string $type): ?RequestLog { $query = RequestLog::query(); if ($type === 'auto') { // 自动检测类型 if (is_numeric($identifier)) { // 纯数字,优先按 ID 查找 $requestLog = $query->where('id', $identifier)->first(); if ($requestLog) { return $requestLog; } } // 按 request_unid 查找 $requestLog = RequestLog::query()->where('request_unid', $identifier)->first(); if ($requestLog) { return $requestLog; } // 按 run_unid 查找 $requestLog = RequestLog::query()->where('run_unid', $identifier)->first(); if ($requestLog) { return $requestLog; } return null; } // 指定类型查找 switch ($type) { case 'id': return $query->where('id', $identifier)->first(); case 'request_unid': return $query->where('request_unid', $identifier)->first(); case 'run_unid': return $query->where('run_unid', $identifier)->first(); default: $this->error("不支持的类型: {$type}"); return null; } } /** * 从 headers JSON 中提取 token * * @param string|null $headersJson * @return string|null */ protected function extractToken(?string $headersJson): ?string { if (empty($headersJson)) { return null; } try { $headers = json_decode($headersJson, true); if (!is_array($headers)) { return null; } // 查找 token 字段(可能在不同的键名下) $tokenKeys = ['token', 'Token', 'authorization', 'Authorization']; foreach ($tokenKeys as $key) { if (isset($headers[$key])) { $tokenValue = $headers[$key]; // headers 中的值可能是数组 if (is_array($tokenValue)) { return $tokenValue[0] ?? null; } return $tokenValue; } } return null; } catch (\Exception $e) { $this->warn("解析 headers 失败: " . $e->getMessage()); return null; } } /** * 初始化 HTTP 客户端 * * @param int $timeout */ protected function initializeHttpClient(int $timeout): void { $this->baseUrl = env('UNITTEST_URL', 'http://localhost:8000'); $this->info("目标地址: {$this->baseUrl}"); $this->client = new Client([ 'base_uri' => $this->baseUrl, 'timeout' => $timeout, 'http_errors' => false, 'verify' => false, // 禁用 SSL 验证 ]); } /** * 发起请求 * * @param string $protobufJson * @param string|null $token * @return array|null */ protected function makeRequest(string $protobufJson, ?string $token): ?array { try { $headers = [ 'Content-Type' => 'application/json', 'Accept' => 'application/json' ]; if ($token) { $headers['token'] = $token; } Log::info('复现请求开始', [ 'url' => $this->baseUrl . '/gameapi', 'headers' => $headers, 'body_length' => strlen($protobufJson) ]); $response = $this->client->post('/gameapi', [ 'body' => $protobufJson, 'headers' => $headers ]); $statusCode = $response->getStatusCode(); $responseHeaders = $response->getHeaders(); $responseBody = $response->getBody()->getContents(); Log::info('复现请求完成', [ 'status_code' => $statusCode, 'response_length' => strlen($responseBody) ]); return [ 'status_code' => $statusCode, 'headers' => $responseHeaders, 'body' => $responseBody ]; } catch (\Exception $e) { $this->error("请求失败: " . $e->getMessage()); Log::error('复现请求失败', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return null; } } /** * 清空当前日志文件 */ protected function clearLogFiles(): void { $this->info("正在清空日志文件..."); try { // 获取当前日志文件路径 $logPath = storage_path('logs'); $currentDate = date('Y-m-d'); // Laravel 默认日志文件名格式 $logFiles = [ $logPath . '/laravel.log', $logPath . "/laravel-{$currentDate}.log", ]; $clearedCount = 0; foreach ($logFiles as $logFile) { if (file_exists($logFile)) { // 清空文件内容但保留文件 file_put_contents($logFile, ''); $clearedCount++; $this->line("已清空: " . basename($logFile)); } } if ($clearedCount > 0) { $this->info("成功清空 {$clearedCount} 个日志文件"); } else { $this->warn("未找到需要清空的日志文件"); } Logger::debug('旧的日志已经清理'); } catch (\Exception $e) { $this->error("清空日志文件失败: " . $e->getMessage()); } } }