|
|
@@ -15,6 +15,7 @@ use UCore\Helper\Logger;
|
|
|
*
|
|
|
* 通过 sys_request_logs 表的记录来复现请求,用于调试和错误排查
|
|
|
* 默认使用 post 字段的原始请求数据,根据 headers 中的 Content-Type 自动检测数据类型
|
|
|
+ * 根据响应的 Content-Type 头智能显示响应内容(JSON、HTML、Protobuf、文本等)
|
|
|
*
|
|
|
* 使用示例:
|
|
|
* php artisan debug:reproduce-error 68981433 # 使用ID查找,自动选择数据源
|
|
|
@@ -40,7 +41,7 @@ class ReproduceErrorCommand extends Command
|
|
|
*
|
|
|
* @var string
|
|
|
*/
|
|
|
- protected $description = '通过请求日志记录复现错误请求,用于调试和问题排查。支持运行前清空日志文件,默认使用post字段数据并根据headers自动检测数据类型';
|
|
|
+ protected $description = '通过请求日志记录复现错误请求,用于调试和问题排查。支持运行前清空日志文件,默认使用post字段数据并根据headers自动检测数据类型,根据响应Content-Type智能显示内容';
|
|
|
|
|
|
/**
|
|
|
* HTTP 客户端
|
|
|
@@ -147,7 +148,7 @@ class ReproduceErrorCommand extends Command
|
|
|
|
|
|
// 输出结果
|
|
|
$this->line("响应内容:");
|
|
|
- $this->displayResponse($response, $requestData['type']);
|
|
|
+ $this->displayResponse($response);
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
@@ -451,36 +452,243 @@ class ReproduceErrorCommand extends Command
|
|
|
/**
|
|
|
* 显示响应内容
|
|
|
*
|
|
|
- * @param array $response 响应数据
|
|
|
- * @param string $requestType 请求类型
|
|
|
+ * @param array $response 响应数据(包含status_code、headers、body)
|
|
|
*/
|
|
|
- protected function displayResponse(array $response, string $requestType): void
|
|
|
+ protected function displayResponse(array $response): void
|
|
|
{
|
|
|
$this->line("状态码: " . $response['status_code']);
|
|
|
|
|
|
- if ($requestType === 'json') {
|
|
|
- // JSON 响应,尝试解析
|
|
|
- $jsonData = json_decode($response['body'], true);
|
|
|
+ // 显示响应头信息
|
|
|
+ $this->line("响应头:");
|
|
|
+ foreach ($response['headers'] as $name => $values) {
|
|
|
+ $value = is_array($values) ? implode(', ', $values) : $values;
|
|
|
+ $this->line(" {$name}: {$value}");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从响应头中提取Content-Type
|
|
|
+ $responseContentType = $this->extractResponseContentType($response['headers']);
|
|
|
+ $this->line("检测到响应Content-Type: " . ($responseContentType ?: '未知'));
|
|
|
+
|
|
|
+ // 根据响应Content-Type决定显示方式
|
|
|
+ $responseBody = $response['body'];
|
|
|
+ $bodyLength = strlen($responseBody);
|
|
|
+
|
|
|
+ $this->line("响应体长度: {$bodyLength} 字节");
|
|
|
+
|
|
|
+ if ($this->isJsonContentType($responseContentType)) {
|
|
|
+ // JSON 响应
|
|
|
+ $this->line("按JSON格式解析响应:");
|
|
|
+ $jsonData = json_decode($responseBody, true);
|
|
|
if ($jsonData !== null) {
|
|
|
dump($jsonData);
|
|
|
} else {
|
|
|
- $this->warn("响应不是有效的 JSON 格式");
|
|
|
- $this->line("原始响应: " . substr($response['body'], 0, 500) . "...");
|
|
|
+ $this->warn("响应Content-Type为JSON但解析失败");
|
|
|
+ $this->displayRawContent($responseBody);
|
|
|
}
|
|
|
- } else {
|
|
|
+ } elseif ($this->isProtobufContentType($responseContentType)) {
|
|
|
// Protobuf 二进制响应
|
|
|
- $this->line("二进制响应长度: " . strlen($response['body']) . " 字节");
|
|
|
- $this->line("Base64 编码: " . substr(base64_encode($response['body']), 0, 200) . "...");
|
|
|
+ $this->line("按Protobuf格式处理响应:");
|
|
|
+ $this->line("Base64 编码: " . substr(base64_encode($responseBody), 0, 200) . "...");
|
|
|
|
|
|
// 尝试解析为 JSON(某些情况下服务器可能返回 JSON)
|
|
|
- $jsonData = json_decode($response['body'], true);
|
|
|
+ $jsonData = json_decode($responseBody, true);
|
|
|
if ($jsonData !== null) {
|
|
|
$this->line("响应可解析为 JSON:");
|
|
|
dump($jsonData);
|
|
|
} else {
|
|
|
$this->line("响应为二进制 Protobuf 数据,无法直接显示");
|
|
|
}
|
|
|
+ } elseif ($this->isHtmlContentType($responseContentType)) {
|
|
|
+ // HTML 响应
|
|
|
+ $this->line("按HTML格式处理响应:");
|
|
|
+ $this->displayHtmlContent($responseBody);
|
|
|
+ } elseif ($this->isTextContentType($responseContentType)) {
|
|
|
+ // 纯文本响应
|
|
|
+ $this->line("按文本格式显示响应:");
|
|
|
+ $this->line($responseBody);
|
|
|
+ } else {
|
|
|
+ // 未知类型,尝试智能检测
|
|
|
+ $this->line("未知Content-Type,尝试智能检测:");
|
|
|
+ $this->smartDisplayContent($responseBody);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从响应头中提取Content-Type
|
|
|
+ *
|
|
|
+ * @param array $headers 响应头数组
|
|
|
+ * @return string|null
|
|
|
+ */
|
|
|
+ protected function extractResponseContentType(array $headers): ?string
|
|
|
+ {
|
|
|
+ // 尝试不同的Content-Type键名(大小写不敏感)
|
|
|
+ $contentTypeKeys = ['Content-Type', 'content-type', 'Content-type', 'CONTENT-TYPE'];
|
|
|
+
|
|
|
+ foreach ($contentTypeKeys as $key) {
|
|
|
+ if (isset($headers[$key])) {
|
|
|
+ $value = $headers[$key];
|
|
|
+ // 如果是数组,取第一个值
|
|
|
+ if (is_array($value)) {
|
|
|
+ $value = $value[0] ?? '';
|
|
|
+ }
|
|
|
+ // 只取分号前的部分(去掉charset等参数)
|
|
|
+ return trim(explode(';', $value)[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否为JSON内容类型
|
|
|
+ *
|
|
|
+ * @param string|null $contentType
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ protected function isJsonContentType(?string $contentType): bool
|
|
|
+ {
|
|
|
+ if (!$contentType) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ $jsonTypes = [
|
|
|
+ 'application/json',
|
|
|
+ 'text/json',
|
|
|
+ 'application/ld+json'
|
|
|
+ ];
|
|
|
+
|
|
|
+ return in_array(strtolower($contentType), $jsonTypes);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否为Protobuf内容类型
|
|
|
+ *
|
|
|
+ * @param string|null $contentType
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ protected function isProtobufContentType(?string $contentType): bool
|
|
|
+ {
|
|
|
+ if (!$contentType) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ $protobufTypes = [
|
|
|
+ 'application/x-protobuf',
|
|
|
+ 'application/protobuf',
|
|
|
+ 'application/octet-stream'
|
|
|
+ ];
|
|
|
+
|
|
|
+ return in_array(strtolower($contentType), $protobufTypes);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否为HTML内容类型
|
|
|
+ *
|
|
|
+ * @param string|null $contentType
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ protected function isHtmlContentType(?string $contentType): bool
|
|
|
+ {
|
|
|
+ if (!$contentType) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ $htmlTypes = [
|
|
|
+ 'text/html',
|
|
|
+ 'application/xhtml+xml'
|
|
|
+ ];
|
|
|
+
|
|
|
+ return in_array(strtolower($contentType), $htmlTypes);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否为文本内容类型
|
|
|
+ *
|
|
|
+ * @param string|null $contentType
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ protected function isTextContentType(?string $contentType): bool
|
|
|
+ {
|
|
|
+ if (!$contentType) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return str_starts_with(strtolower($contentType), 'text/');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示HTML内容
|
|
|
+ *
|
|
|
+ * @param string $content
|
|
|
+ */
|
|
|
+ protected function displayHtmlContent(string $content): void
|
|
|
+ {
|
|
|
+ // 检查是否包含错误页面标识
|
|
|
+ if (str_contains($content, '<title>') && str_contains($content, '</title>')) {
|
|
|
+ preg_match('/<title>(.*?)<\/title>/i', $content, $matches);
|
|
|
+ $title = $matches[1] ?? '未知';
|
|
|
+ $this->line("HTML页面标题: {$title}");
|
|
|
}
|
|
|
+
|
|
|
+ // 显示部分内容
|
|
|
+ $preview = substr(strip_tags($content), 0, 500);
|
|
|
+ $this->line("HTML内容预览: " . $preview . "...");
|
|
|
+
|
|
|
+ // 检查是否为错误页面
|
|
|
+ if (str_contains($content, 'error') || str_contains($content, 'Error') || str_contains($content, 'exception')) {
|
|
|
+ $this->warn("检测到可能的错误页面");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示原始内容
|
|
|
+ *
|
|
|
+ * @param string $content
|
|
|
+ */
|
|
|
+ protected function displayRawContent(string $content): void
|
|
|
+ {
|
|
|
+ $this->line("原始响应内容:");
|
|
|
+ if (strlen($content) > 1000) {
|
|
|
+ $this->line(substr($content, 0, 1000) . "...");
|
|
|
+ $this->line("(内容过长,已截断显示)");
|
|
|
+ } else {
|
|
|
+ $this->line($content);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 智能检测并显示内容
|
|
|
+ *
|
|
|
+ * @param string $content
|
|
|
+ */
|
|
|
+ protected function smartDisplayContent(string $content): void
|
|
|
+ {
|
|
|
+ // 尝试JSON解析
|
|
|
+ $jsonData = json_decode($content, true);
|
|
|
+ if ($jsonData !== null) {
|
|
|
+ $this->line("内容可解析为JSON:");
|
|
|
+ dump($jsonData);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否为HTML
|
|
|
+ if (str_contains($content, '<html') || str_contains($content, '<!DOCTYPE')) {
|
|
|
+ $this->line("检测到HTML内容:");
|
|
|
+ $this->displayHtmlContent($content);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否为二进制数据
|
|
|
+ if (!mb_check_encoding($content, 'UTF-8')) {
|
|
|
+ $this->line("检测到二进制数据:");
|
|
|
+ $this->line("数据长度: " . strlen($content) . " 字节");
|
|
|
+ $this->line("Base64 编码: " . substr(base64_encode($content), 0, 200) . "...");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 默认按文本显示
|
|
|
+ $this->line("按文本格式显示:");
|
|
|
+ $this->displayRawContent($content);
|
|
|
}
|
|
|
|
|
|
/**
|