apiSSE.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. <?php
  2. header("Access-Control-Allow-Origin: *"); // 允许所有来源
  3. header("Access-Control-Allow-Methods: GET"); // 允许的 HTTP 方法
  4. header("Access-Control-Allow-Headers: Content-Type"); // 允许的请求头
  5. // 强制禁用所有缓冲
  6. while (ob_get_level()) ob_end_clean();
  7. header('X-Accel-Buffering: no');
  8. // 设置 SSE 头部
  9. header("Content-Type: text/event-stream");
  10. header("Cache-Control: no-cache");
  11. header("Connection: keep-alive");
  12. header("X-Accel-Buffering: no");
  13. // 配置执行环境
  14. set_time_limit(0);
  15. ignore_user_abort(false);
  16. //创建公共url
  17. $urlCommon = 'http://192.168.10.115:3338';
  18. $mcp = $_GET['mcpid'];
  19. $thinkid = $_GET['thinkid'];
  20. $zhikong = $_GET['zhikong'];
  21. //获取对话短令牌
  22. $input['token'] = $_GET['token'];
  23. $dataReply['token'] = $_GET['token'];
  24. $token = $_GET['token'];
  25. $ragIdGet = $_GET['ragId'];
  26. if(($ragIdGet == 'null') || ($ragIdGet == null)){
  27. $input['ragId'] = 'empty';
  28. }
  29. else{
  30. $input['ragId'] = $ragIdGet;
  31. }
  32. // $input['ragId'] = 'empty';
  33. // $mcp =="";
  34. // if($mcp != ""){
  35. // echo("daddsda");
  36. // }
  37. // echo($mcp);
  38. // exit;
  39. //将请求的完整内容发送至api控制器鉴权
  40. $urlCheck = $urlCommon.'/apis/check';
  41. $information = curlPost($urlCheck,$input);
  42. //判断鉴权的结果,如果鉴权失败,直接退出
  43. $informationObject = json_decode($information);
  44. if(($informationObject->code) != 5){
  45. echo "event: verify\ndata: $information\n\n";
  46. exit;
  47. }
  48. //获取最终的核心参数参与模型输入
  49. $modelId = $informationObject->modelId;
  50. $content = $informationObject->input;
  51. $contentmcp = $informationObject->input;
  52. $ragId = $informationObject->ragId;
  53. // if(is_array($content)){
  54. // $content = json_encode($content,JSON_UNESCAPED_UNICODE);
  55. // }
  56. // 模型定制开始-------------------------------------------------------------------
  57. // //emoon-E1-13B线上
  58. // if($modelId == 9){
  59. // $apiKey = 'sk-800990e9cdb34613a2133f0fc333a7ff'; // 请替换为你的实际 API 密钥
  60. // $url = 'https://api.deepseek.com/chat/completions';
  61. // $arraySystem = [[
  62. // 'role' => 'system',
  63. // 'content' => '你的名字叫EMOON E1 13B模型,你由回车网络(EnterLO)训练研发,你并不是deepseek模型。你是一名三甲医院主治医师级别的AI助手,最擅长医疗专业知识的解读和分析,遵循《中国临床诊疗指南》和NCCN指南。必须:1) 先询问关键症状细节 2) 标注证据等级 3) 拒绝非循证医学建议'
  64. // ]];
  65. // // $arrayContent = json_decode($content);
  66. // $arrayMessage = array_merge($arraySystem,$content);
  67. // // 请求数据
  68. // $data = [
  69. // 'model' => 'deepseek-chat',
  70. // 'messages' => $arrayMessage,
  71. // 'stream' => true
  72. // ];
  73. // // 设置请求头
  74. // $headers = [
  75. // 'Content-Type: application/json',
  76. // 'Authorization: Bearer ' . $apiKey
  77. // ];
  78. // }
  79. if($ragId != ''){
  80. $content = stdClassObjToArray($content);
  81. $dataRag['input'] = $content[count($content)-1]['content'];
  82. $dataRag['kid'] = $ragId;
  83. $urlRAG = 'http://192.168.10.115:3338/apis/ragData';
  84. $ragResult = curlPost($urlRAG,$dataRag);
  85. $ragResult = json_decode($ragResult);
  86. $ragResult = stdClassObjToArray($ragResult);
  87. // if( $ragResult['code'] == 200){
  88. // $ragInfomation = json_encode($ragResult['nearest']);
  89. // }
  90. if(json_encode($ragResult['nearest'],JSON_UNESCAPED_UNICODE) != "[]" ){
  91. if($ragId == "1947484295610253313" || $ragId == "1949641433623703553"){
  92. $content[count($content)-1]['content'] = '我的提问是:“'.$content[count($content)-1]['content'].'”,下面是基于我的提问查到的知识库相关内容:“'.json_encode($ragResult['nearest'],JSON_UNESCAPED_UNICODE).'”,请按照查到的知识库相关内容,进行病历质控,根据知识库相关内容条件生成一个包含规则,根据规则数、扣除分数和原因列表。输出结果需要以 JSON 格式呈现,例如:
  93. 规则 1:主诉症状明确性(3分)标准与细则:必须描述具体症状或体征(如“头痛”,“发热”),禁用诊断名称(如“糖尿病”)。扣分细则:若主诉中使用了诊断名称或模糊描述,扣3分,反之不扣分。
  94. 规则 2:主诉时间准确性(2分)标准与细则:用阿拉伯数字+单位(如“3天”),禁用“三天”“数月”等模糊表述。扣分细则:若主诉时间使用模糊表述(如“三天”“数月”),扣2分,反之不扣分。
  95. 返回的数据应按照以下格式组织:
  96. [
  97. {
  98. "规则": "规则1",
  99. "扣除分数": 3,
  100. "原因": "原因详细说明"
  101. },
  102. {
  103. "规则": "规则2",
  104. "扣除分数": 2,
  105. "原因": "原因详细说明"
  106. }
  107. ]
  108. 只按照格式输出,不要输出其他内容';//请求向量数据库接口反参组装
  109. $contentmcp = $content;
  110. }
  111. else{
  112. $content[count($content)-1]['content'] = '我的提问是:“'.$content[count($content)-1]['content'].'”,下面是基于我的提问查到的知识库相关内容:“'.json_encode($ragResult['nearest'],JSON_UNESCAPED_UNICODE).'”。';//请求向量数据库接口反参组装
  113. $contentmcp = $content;
  114. }
  115. }
  116. }
  117. // $content = stdClassObjToArray($content);
  118. // echo(json_encode($ragResult,JSON_UNESCAPED_UNICODE));
  119. // exit;
  120. if($thinkid != ''){
  121. $urlgetreply = $urlCommon.'/apis/getreply';
  122. $reply = curlPost($urlgetreply,$dataReply);
  123. $content = stdClassObjToArray($content);
  124. $content[count($content)-1]['content'] = '我的提问是:“'.$content[count($content)-1]['content'].'”,下面是基于我的提问得到的回答:“'.$reply.'”,现在请根据回答生成一个思考过程,严格遵守以下要求:只回复思考过程,不输出其他内容。';//请求向量数据库接口反参组装
  125. }
  126. // echo(json_encode($content,JSON_UNESCAPED_UNICODE));
  127. // exit;
  128. //deepseek-R1-70B线下
  129. if($modelId == 12){
  130. $url = 'http://192.168.10.115:19997/v1/chat/completions';
  131. $headers = [
  132. 'accept: application/json',
  133. 'Content-Type: application/json'
  134. ];
  135. $arraySystem = [[
  136. 'role' => 'system',
  137. 'content' => '你是一个人工智能助手。'
  138. ],
  139. ];
  140. //$arrayContent = json_decode($content, true);
  141. $arrayMessage = array_merge($arraySystem, $content);
  142. $data = [
  143. "messages" => $arrayMessage,
  144. "model" => "qwen3-32B", //X
  145. "stream" => true,
  146. "max_tokens" => 8192,
  147. "chat_template_kwargs" => '{"enable_thinking": false}',
  148. ];
  149. }
  150. // echo(json_encode($data,JSON_UNESCAPED_UNICODE));
  151. // exit;
  152. // 模型定制结束-------------------------------------------------------------------
  153. if($mcp == 0 || $mcp ==""){
  154. $ch = curl_init();
  155. curl_setopt($ch, CURLOPT_URL, $url);
  156. curl_setopt($ch, CURLOPT_POST, true);
  157. curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data,JSON_UNESCAPED_UNICODE));
  158. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  159. curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
  160. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
  161. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
  162. curl_setopt_array($ch, [
  163. CURLOPT_TIMEOUT => 60, // 超时时间延长至60秒
  164. CURLOPT_TCP_KEEPALIVE => 1, // 启用TCP保活
  165. CURLOPT_TCP_KEEPIDLE => 10,
  166. CURLOPT_TCP_KEEPINTVL => 5
  167. ]);
  168. //创建一个空字符串动态存储回复的文字
  169. $reply = '';
  170. // 添加连接状态检查到写入回调
  171. curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use (&$reply,$thinkid,$zhikong) {
  172. // 如果客户端已断开,立即终止
  173. if (connection_aborted()) {
  174. return -1; // 返回-1会强制CURL中断请求
  175. curl_close($ch);
  176. }
  177. $lines = explode("\n", $data);
  178. foreach ($lines as $line) {
  179. if (strpos($line, 'data:') === 0) {
  180. $contents = trim(substr($line, 5));
  181. if ($contents === '[DONE]') {
  182. $output = "event: end\ndata: {\"status\":\"done\"}\n\n";
  183. echo $output;
  184. // $reply .= $output; // 捕获输出内容
  185. //完成输出后触发全部回复存储进入数据库
  186. global $token;
  187. global $urlCommon;
  188. $dataReply['token'] = $token;
  189. $dataReply['reply'] = $reply;
  190. //将数据发送至数据库
  191. $curlReplay = curl_init();
  192. curl_setopt($curlReplay, CURLOPT_URL, $urlCommon.'/Apis/reply');
  193. curl_setopt($curlReplay, CURLOPT_SSL_VERIFYPEER, FALSE);
  194. curl_setopt($curlReplay, CURLOPT_SSL_VERIFYHOST, FALSE);
  195. // curl_setopt($curl, CURLOPT_HTTPHEADER,$headers);
  196. if ($json) {
  197. curl_setopt($curlReplay,CURLOPT_HTTPHEADER,[
  198. "Content-Type: application/json"
  199. ]);
  200. curl_setopt($curlReplay, CURLOPT_POST, TRUE);
  201. curl_setopt($curlReplay,CURLOPT_POSTFIELDS,json_encode($dataReply));
  202. }else {
  203. if (!empty($dataReply)) {
  204. curl_setopt($curlReplay, CURLOPT_POST, TRUE);
  205. curl_setopt($curlReplay, CURLOPT_POSTFIELDS,$dataReply);
  206. // curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  207. }
  208. }
  209. curl_setopt($curlReplay, CURLOPT_RETURNTRANSFER,true);
  210. curl_exec($curlReplay);
  211. curl_close($curlReplay);
  212. } else {
  213. // 转发数据前再次检查连接
  214. if (connection_aborted()) {
  215. return -1;
  216. curl_close($ch);
  217. }
  218. //构造$line
  219. global $modelId;
  220. $line = substr($line,6);
  221. $line = json_decode($line);
  222. $line = stdClassObjToArray($line);
  223. if($modelId == 9){
  224. $line['model'] = 'emoon-E1-13B';
  225. }
  226. $line = 'data: '.json_encode($line,JSON_UNESCAPED_UNICODE);
  227. $contentlines = trim(substr($line, 6));
  228. $jsonline = json_decode($contentlines, true);
  229. $jsoncontent = $jsonline['choices'][0]['delta']['content'];
  230. if (($jsoncontent == "</") || ($jsoncontent == 'answer') || ($jsoncontent == "。<") || ($jsoncontent == ">") || ($jsoncontent == "<")) {
  231. $output = 'data: {"id":"chatcmpl-1747186801165865728","object":"chat.completions.chunk","created":1747186801,"model":"merge","choices":[{"index":0,"finish_reason":null,"delta":{"role":"assistant","content":""}}]}'. "\n\n";
  232. // echo $ouputs."\n";
  233. // $output = "event: end\ndata: Stream ended\n\n";
  234. }
  235. elseif($jsonline['choices'][0]['finish_reason'] === 'stop'){
  236. // $output = "event: end\ndata: Stream ended\n\n";
  237. if($thinkid == null && $zhikong == 1){
  238. $output = 'data: {"id":"chatcmpl-1747186801165865728","object":"chat.completions.chunk","created":1747186801,"model":"merge","choices":[{"index":0,"finish_reason":null,"delta":{"role":"assistant","content":"ending"}}]}'. "\n\n";
  239. }else{
  240. $output = 'data: {"id":"chatcmpl-1747186801165865728","object":"chat.completions.chunk","created":1747186801,"model":"merge","choices":[{"index":0,"finish_reason":null,"delta":{"role":"assistant","content":""}}]}'. "\n\n";
  241. }
  242. //完成输出后触发全部回复存储进入数据库
  243. global $token;
  244. global $urlCommon;
  245. $dataReply['token'] = $token;
  246. $dataReply['reply'] = $reply;
  247. //将数据发送至数据库
  248. $curlReplay = curl_init();
  249. curl_setopt($curlReplay, CURLOPT_URL, $urlCommon.'/Apis/reply');
  250. curl_setopt($curlReplay, CURLOPT_SSL_VERIFYPEER, FALSE);
  251. curl_setopt($curlReplay, CURLOPT_SSL_VERIFYHOST, FALSE);
  252. // curl_setopt($curl, CURLOPT_HTTPHEADER,$headers);
  253. if ($json) {
  254. curl_setopt($curlReplay,CURLOPT_HTTPHEADER,[
  255. "Content-Type: application/json"
  256. ]);
  257. curl_setopt($curlReplay, CURLOPT_POST, TRUE);
  258. curl_setopt($curlReplay,CURLOPT_POSTFIELDS,json_encode($dataReply));
  259. }else {
  260. if (!empty($dataReply)) {
  261. curl_setopt($curlReplay, CURLOPT_POST, TRUE);
  262. curl_setopt($curlReplay, CURLOPT_POSTFIELDS,$dataReply);
  263. // curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  264. }
  265. }
  266. curl_setopt($curlReplay, CURLOPT_RETURNTRANSFER,true);
  267. curl_exec($curlReplay);
  268. curl_close($curlReplay);
  269. }
  270. else{
  271. $output = $line . "\n\n";
  272. }
  273. // $output = $line . "\n\n";
  274. echo $output;
  275. //将回复数据存储入$reply
  276. if($thinkid == null){
  277. $outputJson = substr($output,6);
  278. if(($modelId == 9) || ($modelId == 7) || ($modelId == 4) || ($modelId == 10) || ($modelId == 12) || ($modelId == 15)){
  279. $outputJson = json_decode($outputJson);
  280. $outputJson = $outputJson->choices[0]->delta->content;
  281. if($outputJson){
  282. $outputJson = json_encode($outputJson,JSON_UNESCAPED_UNICODE);
  283. $outputJson = str_replace('"', '', $outputJson);
  284. }
  285. }
  286. }
  287. $reply .= $outputJson; // 捕获输出内容
  288. }
  289. ob_flush();
  290. flush();
  291. }
  292. }
  293. return strlen($data);
  294. });
  295. // 错误处理
  296. curl_setopt($ch, CURLOPT_TIMEOUT, 86400);
  297. curl_setopt($ch, CURLOPT_FAILONERROR, true);
  298. // 执行请求
  299. try {
  300. curl_exec($ch);
  301. // 检查HTTP状态码
  302. $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  303. if ($statusCode !== 200) {
  304. echo "event: error\ndata: HTTP Status {$statusCode}\n\n";
  305. ob_flush();
  306. flush();
  307. }
  308. } catch (Exception $e) {
  309. echo "event: error\ndata: " . $e->getMessage() . "\n\n";
  310. ob_flush();
  311. flush();
  312. }
  313. }else{
  314. @ini_set('output_buffering', 'off');
  315. @ini_set('zlib.output_compression', false);
  316. while (ob_get_level() > 0) {
  317. ob_end_flush();
  318. }
  319. ob_implicit_flush(true);
  320. // 设置流式响应头
  321. $requestBody = file_get_contents('php://input');
  322. $headers = getallheaders();
  323. $authorizationHeader = isset($headers['Authorization']) ? $headers['Authorization'] : '';
  324. $reply = '';
  325. $RAGdata = [
  326. "username" => "test",
  327. "password" => "test123"
  328. ];
  329. // 调用时只需传递非 Content-Type/Accept 的 Header
  330. $headers = [
  331. 'Cache-Control: no-cache',
  332. 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
  333. ];
  334. $urlRAG = "http://192.168.10.115:6039/auth/login"; // 替换为实际 URL
  335. $id = curlPostRAG($urlRAG, $RAGdata, $headers);
  336. $token = $id; // 默认启用 JSON 模式
  337. $token = json_decode($token);
  338. $token = stdClassObjToArray($token);
  339. $token = $token['data']['token'];
  340. $url = "http://192.168.10.115:6039/chat/emoonSend"; // 替换成实际接口地址
  341. $data = [
  342. "messages" => $contentmcp,
  343. "model" => "qwen3-32B", //X
  344. "temperature" => 0.5,
  345. "top_p" => 1,
  346. "presence_penalty" => 0,
  347. "frequency_penalty" => 0,
  348. "kid" => "",
  349. "chat_type" => 0,
  350. "appId" => "",
  351. "agentId" => $mcp,
  352. "stream" => true
  353. ];
  354. // 初始化 cURL
  355. $ch = curl_init();
  356. // 设置 cURL 选项
  357. curl_setopt($ch, CURLOPT_URL, $url);
  358. curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); // 返回响应数据
  359. curl_setopt($ch, CURLOPT_POST, true); // 使用 POST 方法
  360. curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); // 发送 JSON 数据
  361. curl_setopt($ch, CURLOPT_HTTPHEADER, [
  362. "Authorization: Bearer " . $token,
  363. "Content-Type: application/json" // 声明请求体是 JSON
  364. ]);
  365. $chatId = 'chat' . uniqid() . bin2hex(random_bytes(8));
  366. $created = time();
  367. // for ($i = 0; $i < count($lines); $i++) {
  368. // echo $lines[0];
  369. // }
  370. $lastContent = '';
  371. curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use ($chatId, $created, &$lastContent,&$reply) {
  372. $lines = explode("\n", $data);
  373. $prefixLength = strlen("event:chat_completion");
  374. $content = str_replace("event:chat_completion", "", $lines[0]);
  375. // foreach ($lines as $line) {
  376. // $line = substr($line,6);
  377. // $content = $matches[0];
  378. // // 仅当内容变化时才输出
  379. // if ($content !== $lastContent) {
  380. // $lastContent = $content;
  381. $jsonData = [
  382. "id" => $chatId,
  383. "model" => "emoon-E1-13B",
  384. "created" => $created,
  385. "object" => "chat.completion.chunk",
  386. "choices" => [
  387. [
  388. "index" => 0,
  389. "delta" => [
  390. "content" => $content
  391. ],
  392. "finish_reason" => null
  393. ]
  394. ],
  395. "usage" => null
  396. ];
  397. echo "data: " . json_encode($jsonData,JSON_UNESCAPED_UNICODE) . "\n\n";
  398. $outputJson = json_encode($jsonData,JSON_UNESCAPED_UNICODE);
  399. $outputJson = json_decode($outputJson);
  400. // $outputJson = stdClassObjToArray($outputJson);
  401. $outputJson = $outputJson->choices[0]->delta->content;
  402. if($outputJson){
  403. $outputJson = json_encode($outputJson,JSON_UNESCAPED_UNICODE);
  404. $outputJson = str_replace('"', '', $outputJson);
  405. }
  406. $reply .= $outputJson;
  407. // }
  408. @ob_flush();
  409. @flush();
  410. // }
  411. return strlen($data);
  412. });
  413. $res = curl_exec($ch);
  414. }
  415. // 清理资源
  416. curl_close($ch);
  417. // 显式结束流
  418. echo "event: end\ndata: Stream ended\n\n";
  419. ob_flush();
  420. flush();
  421. $dataReply['reply'] = $reply;
  422. $url=$urlCommon.'/Apis/reply';
  423. curlPost($url,$dataReply);
  424. //模型定制结束-------------------------------------------------------------------
  425. function curlPost($url, $data = null, $headers = [], $json = false) {
  426. $curl = curl_init($url);
  427. $options = [
  428. CURLOPT_RETURNTRANSFER => true,
  429. CURLOPT_SSL_VERIFYPEER => false,
  430. CURLOPT_SSL_VERIFYHOST => false,
  431. CURLOPT_POST => !empty($data) || $json,
  432. ];
  433. if ($json) {
  434. $headers = array_filter($headers, function($h) {
  435. return stripos($h, 'Content-Type:') === false;
  436. });
  437. $headers[] = 'Content-Type: application/json';
  438. $data = json_encode($data, JSON_UNESCAPED_UNICODE);
  439. }
  440. if (!empty($headers)) {
  441. $options[CURLOPT_HTTPHEADER] = $headers;
  442. }
  443. if (!empty($data)) {
  444. $options[CURLOPT_POSTFIELDS] = $data;
  445. }
  446. curl_setopt_array($curl, $options);
  447. $result = curl_exec($curl);
  448. curl_close($curl);
  449. return $result;
  450. }
  451. function stdClassObjToArray($array) {
  452. if(is_object($array)) {
  453. $array = (array)$array;
  454. }
  455. if(is_array($array)) {
  456. foreach($array as $key=>$value) {
  457. $array[$key] = stdClassObjToArray($value);
  458. }
  459. }
  460. return $array;
  461. }
  462. function curlPostRAG($url, $data = null, $headers = [], $json = true) {
  463. $curl = curl_init($url);
  464. $options = [
  465. CURLOPT_RETURNTRANSFER => true,
  466. CURLOPT_SSL_VERIFYPEER => false,
  467. CURLOPT_SSL_VERIFYHOST => false,
  468. CURLOPT_POST => true, // 强制启用 POST(JSON 模式下始终 POST)
  469. ];
  470. if ($json) {
  471. // 移除已有的 Content-Type 和 Accept 头,避免冲突
  472. $headers = array_filter($headers, function($h) {
  473. return stripos($h, 'Content-Type:') === false && stripos($h, 'Accept:') === false;
  474. });
  475. // 添加 JSON 专用头
  476. $headers[] = 'Content-Type: application/json';
  477. $headers[] = 'Accept: application/json';
  478. // 编码数据为 JSON(空数据编码为 "{}" 避免服务器解析错误)
  479. $data = $data !== null ? json_encode($data, JSON_UNESCAPED_UNICODE) : '{}';
  480. }
  481. // 设置请求头和数据
  482. if (!empty($headers)) {
  483. $options[CURLOPT_HTTPHEADER] = $headers;
  484. }
  485. if ($data !== null) {
  486. $options[CURLOPT_POSTFIELDS] = $data;
  487. }
  488. curl_setopt_array($curl, $options);
  489. $result = curl_exec($curl);
  490. // 错误处理(调试时启用)
  491. if ($result === false) {
  492. $error = curl_error($curl);
  493. $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  494. throw new Exception("CURL请求失败: HTTP状态码 $httpCode, 错误信息: $error");
  495. }
  496. curl_close($curl);
  497. return $result;
  498. }
  499. exit;