'POST', 'callback' => [$this, 'handle_chat'], 'permission_callback' => ['Antom_Shomi_Setup_Wizard', 'check_api_permission'], 'args' => [], ]); } /** * 处理聊天请求(流式 SSE 代理) * * 前端发送 POST 请求,本接口将请求转发到 Antom Bot Chat API, * 并以 Server-Sent Events (SSE) 流式方式将响应逐块返回给前端。 * * @param WP_REST_Request $request * @return void|WP_REST_Response */ public function handle_chat(WP_REST_Request $request) { $body = $request->get_json_params(); if (!is_array($body)) { return new WP_REST_Response( ['success' => false, 'message' => 'Invalid JSON body'], 400 ); } // 必填参数校验 $question = isset($body['question']) ? sanitize_text_field($body['question']) : ''; if (empty($question)) { return new WP_REST_Response( ['success' => false, 'message' => 'Missing required field: question'], 400 ); } $session_id = isset($body['sessionId']) ? sanitize_text_field($body['sessionId']) : ''; $external_user = isset($body['externalUserId']) ? sanitize_text_field($body['externalUserId']) : ''; // 从请求 Header 中获取 Cookie,放入 extraMap.platformSession $platform_session = $request->get_header('cookie'); // 构造发送给 Antom Bot Chat API 的请求体 $upstream_body = [ 'customScene' => 'paymentAgent', 'sessionId' => $session_id, 'externalUserId' => $external_user, 'question' => $question, 'extraMap' => [ 'greet' => '', 'responseType' => 'json', 'messageFrom' => 'copilot', 'submitType' => 'normal', 'questionContext' => isset($body['extraMap']['questionContext']) ? $body['extraMap']['questionContext'] : ['scene' => '', 'currentUrl' => '', 'fileList' => []], 'from' => 'paymentAgent', 'platformSession' => $platform_session, ], ]; $upstream_json = wp_json_encode($upstream_body); if (false === $upstream_json) { return new WP_REST_Response( ['success' => false, 'message' => 'Failed to encode request body'], 500 ); } // 使用 cURL 发起流式请求并逐块转发给客户端 $this->stream_upstream_response($upstream_json); } /** * 通过 WordPress HTTP API 发起流式请求,按完整 SSE 消息边界实时输出到客户端 * * 利用 WordPress 提供的 'http_api_curl' 钩子注入 CURLOPT_WRITEFUNCTION, * 实现边接收边输出的真正流式 SSE 代理,同时满足 WordPress 代码规范。 * * @param string $upstream_json 发送给上游 API 的 JSON 字符串 * @return void */ private function stream_upstream_response($upstream_json) { // 设置 SSE 响应头 header('Content-Type: text/event-stream; charset=UTF-8'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); header('X-Accel-Buffering: no'); // 禁用 PHP 输出缓冲 while (ob_get_level() > 0) { ob_end_flush(); } if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } ini_set('zlib.output_compression', '0'); // SSE 行缓冲,只在完整消息边界 flush $buffer = ''; $stream_error = ''; /** * 通过 http_api_curl 钩子注入流式写回调 * * WordPress 在使用 cURL transport 时会触发此钩子,允许修改 cURL handle 选项。 * 我们利用此机制注入 WRITEFUNCTION 实现实时 SSE 输出。 */ $streaming_callback = function (&$curl_handle) use (&$buffer, &$stream_error) { curl_setopt($curl_handle, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$buffer) { $buffer .= $chunk; // SSE 消息以 "\n\n" 分隔,逐条输出完整消息 while (($boundary = strpos($buffer, "\n\n")) !== false) { $message = substr($buffer, 0, $boundary + 2); $buffer = substr($buffer, $boundary + 2); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Raw SSE passthrough from trusted upstream API; content is JSON-encoded event data. echo $message; if (ob_get_level() > 0) { ob_flush(); } flush(); } return strlen($chunk); }); }; // 注册钩子(一次性使用,优先级高以确保最后执行) add_action('http_api_curl', $streaming_callback, 99); // 使用 WordPress HTTP API 发起请求 $response = wp_remote_post(self::BOT_CHAT_URL, [ 'body' => $upstream_json, 'headers' => [ 'Content-Type' => 'application/json', 'Accept' => '*/*', 'appname' => 'paymentAgent', 'Cache-Control' => 'no-cache', 'Origin' => 'http://paymentagent.alipay.net', 'Referer' => 'http://paymentagent.alipay.net/', ], 'timeout' => 120, ]); // 移除钩子,防止影响后续请求 remove_action('http_api_curl', $streaming_callback, 99); // 输出缓冲区中剩余的数据(如果有) if (!empty($buffer)) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Raw SSE passthrough from trusted upstream API. echo $buffer; if (ob_get_level() > 0) { ob_flush(); } flush(); } // 处理请求错误 if (is_wp_error($response)) { $error_data = wp_json_encode([ 'success' => false, 'message' => 'Upstream request failed: ' . $response->get_error_message(), ]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON-encoded error message for SSE stream. echo 'data: ' . $error_data . "\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } // 结束请求,阻止 WordPress 继续输出 exit; } }