下面给你一套在 PHP 里用 cURL 发送 Content-Type: application/json 的 POST 请求的“标准企业写法”:结构清晰、可复用、可排障,适合直接丢进生产代码库🙂
一、最关键的点(先把坑堵死)
请求体必须是 JSON 字符串:用
json_encode()生成,而不是http_build_query()。必须显式设置 Content-Type:
Content-Type: application/json; charset=utf-8。建议带上 Content-Length:部分网关/安全设备更“挑剔”。
服务端返回如果是 JSON,建议设置 Accept: application/json,便于协商。
二、可直接复用的完整示例(含超时、错误处理、响应解析)✅
<?php $url = "https://api.example.com/v1/order/create"; $payload = [ "user_id" => 10001, "amount" => 99.50, "remark" => "test", ]; // 1) 将数组编码成 JSON 字符串(这是 application/json 的核心) $jsonBody = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($jsonBody === false) { throw new RuntimeException("JSON 编码失败: " . json_last_error_msg()); } $ch = curl_init($url); // 2) 关键请求头:明确告诉服务端“我发的是 JSON”,也表明“我期望 JSON 响应” $headers = [ "Content-Type: application/json; charset=utf-8", "Accept: application/json", "Content-Length: " . strlen($jsonBody), ]; curl_setopt_array($ch, [ CURLOPT_POST => true, // 使用 POST 方法 CURLOPT_POSTFIELDS => $jsonBody, // POST 请求体:必须是 JSON 字符串 CURLOPT_HTTPHEADER => $headers, // 设置请求头 CURLOPT_RETURNTRANSFER => true, // 返回响应内容为字符串(不直接输出) CURLOPT_CONNECTTIMEOUT => 5, // 连接超时(秒) CURLOPT_TIMEOUT => 15, // 总超时(秒) CURLOPT_FAILONERROR => false, // 不让 4xx/5xx 直接导致 curl_exec 返回 false(方便读响应体排错) ]); $responseBody = curl_exec($ch); // 3) 网络层错误:DNS、连接失败、TLS 握手失败等 if ($responseBody === false) { $errno = curl_errno($ch); $error = curl_error($ch); curl_close($ch); throw new RuntimeException("cURL 请求失败 [{$errno}]: {$error}"); } // 4) 协议层信息:HTTP 状态码、Content-Type 等 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); curl_close($ch); // 5) 按需解析 JSON 响应(如果服务端确实返回 JSON) $data = null; if (is_string($contentType) && stripos($contentType, "application/json") !== false) { $data = json_decode($responseBody, true); } // 6) 业务层处理示例:按 HTTP 状态码做分流 if ($httpCode >= 200 && $httpCode < 300) { // 成功:优先用解析后的数组;解析失败则回退原始响应 var_dump($data ?? $responseBody); } else { // 失败:把响应体也带上,便于排查(网关经常会回 JSON 错误详情) throw new RuntimeException("HTTP 请求失败,状态码={$httpCode},响应={$responseBody}"); }逐段解释(对应你最常踩的点)
json_encode(...):把 PHP 数组变成 JSON 字符串,这一步决定服务端能不能按 JSON 解析。CURLOPT_POSTFIELDS:你传进去什么,HTTP Body 就是什么。要发 JSON,就必须传 JSON 字符串。CURLOPT_HTTPHEADER:这里把 Content-Type 定死为application/json,否则服务端可能按表单解析导致字段丢失。CURLOPT_RETURNTRANSFER:拿到响应字符串,方便你写日志、做重试、做告警。CURLINFO_HTTP_CODE:把网络成功与业务成功分开看,避免“curl 成功但业务失败还当成功”。🙂
三、工作流程图(你可以当排障 checklist)
准备 payload(数组) ↓ <span style="color:red">json_encode</span> → 得到 JSON 字符串 ↓ 设置请求头:<span style="color:red">Content-Type: application/json</span> ↓ cURL POST 发送(超时、返回响应) ↓ 检查:curl_exec 是否 false(网络层) ↓ 检查:HTTP 状态码(协议层) ↓ 按 Content-Type 判断是否 json_decode(业务层)四、原理/参数对照表(把配置变成可控资产)
| 项 | 推荐值 | 作用 | 常见问题 |
|---|---|---|---|
| Content-Type | application/json; charset=utf-8 | 声明 Body 格式 | 不设置会被当表单解析 |
| CURLOPT_POSTFIELDS | JSON 字符串 | 实际请求体 | 传数组会被转成a=1&b=2 |
| Accept | application/json | 期望响应格式 | 响应非 JSON 时解析失败 |
| CONNECTTIMEOUT | 3~5s | 连接超时 | DNS/TLS 卡住拖垮线程 |
| TIMEOUT | 10~30s | 总超时 | 无上限会把 PHP-FPM 拖死 |
你拿去直接用的“结论”
只要做到两件事:
1)json_encode()得到 JSON Body;
2)请求头带上Content-Type: application/json;
再配好超时与错误分层(网络/HTTP/业务),这就是一套可在生产稳定跑的写法。🚀