HTTP {$httpCode}"; error_log($logMessage); if (!empty($requestData)) { error_log("📤 Request: " . json_encode($requestData, JSON_UNESCAPED_UNICODE)); } error_log("📥 Response: {$response}"); } /** * 设置默认超时时间 * * @param int $timeout 超时时间(秒) */ public static function setDefaultTimeout(int $timeout): void { self::$defaultTimeout = $timeout; } /** * 发送 HTTP POST 请求(原始 JSON 字符串) * 用于某些字段需要预编码的场景(如飞书消息的 content 字段) * * @param string $url 请求URL * @param array $data 请求数据(某些值可以是预编码的 JSON 字符串) * @param array $preencodedKeys 需要预编码的键名数组 * @param array $headers 请求头 * @param int $timeout 超时时间(秒) * @return array 响应数据(JSON解码后的数组) * @throws Exception */ public static function httpPostJson( string $url, array $data, array $preencodedKeys = [], array $headers = [], int $timeout = 0 ): array { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); // 手动构建 JSON,预编码字段作为字符串值直接插入 $jsonParts = []; foreach ($data as $key => $value) { $keyJson = json_encode($key, JSON_UNESCAPED_UNICODE); if (in_array($key, $preencodedKeys)) { // 预编码的字段:值已经是 JSON 字符串,需要作为字符串值插入 // JSON_UNESCAPED_UNICODE 保留 emoji // JSON_UNESCAPED_SLASHES 避免再次转义 URL 中的 / $jsonParts[] = $keyJson . ':' . json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } else { // 其他字段正常编码 $jsonParts[] = $keyJson . ':' . json_encode($value, JSON_UNESCAPED_UNICODE); } } $jsonPayload = '{' . implode(',', $jsonParts) . '}'; // Debug: log the actual payload being sent error_log('🚀 Actual curl payload: ' . $jsonPayload); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ?: self::$defaultTimeout); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $defaultHeaders = [ 'Content-Type: application/json', ]; curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($defaultHeaders, $headers)); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); // PHP 8.0+ curl 资源自动关闭,无需 curl_close() self::logRequest('POST', $url, $data, $response, $httpCode); if ($error) { throw new Exception("HTTP POST 请求失败: {$error}"); } if ($httpCode !== 200) { throw new Exception("HTTP POST 请求失败: HTTP {$httpCode}, Response: {$response}"); } $result = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception('JSON 解析失败: ' . $response); } return $result; } /** * 发送 HTTP PATCH JSON 请求 * * @param string $url 请求URL * @param array $data 请求数据(将被编码为 JSON) * @param array $preencodedKeys 需要预编码的字段名列表(这些字段的值已经是 JSON 字符串) * @param array $headers 额外的请求头 * @param int $timeout 超时时间(秒) * @return array 响应数据(JSON解码后的数组) * @throws Exception */ public static function httpPatchJson( string $url, array $data, array $preencodedKeys = [], array $headers = [], int $timeout = 0 ): array { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH'); // 手动构建 JSON,预编码字段作为字符串值直接插入 $jsonParts = []; foreach ($data as $key => $value) { $keyJson = json_encode($key, JSON_UNESCAPED_UNICODE); if (in_array($key, $preencodedKeys)) { $jsonParts[] = $keyJson . ':' . json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } else { $jsonParts[] = $keyJson . ':' . json_encode($value, JSON_UNESCAPED_UNICODE); } } $jsonPayload = '{' . implode(',', $jsonParts) . '}'; curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ?: self::$defaultTimeout); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $defaultHeaders = [ 'Content-Type: application/json', ]; curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($defaultHeaders, $headers)); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); // PHP 8.0+ curl 资源自动关闭,无需 curl_close() self::logRequest('PATCH', $url, $data, $response, $httpCode); if ($error) { throw new Exception("HTTP PATCH 请求失败: {$error}"); } if ($httpCode !== 200) { throw new Exception("HTTP PATCH 请求失败: HTTP {$httpCode}, Response: {$response}"); } $result = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception('JSON 解析失败: ' . $response); } return $result; } }