在 ThinkPHP6 的 think-swoole 框架下,如果我们直接使用 Swoole 的底层事件 onRequest 来挂载并解析标准的 JsonRPC 2.0 协议,通常是因为要兼容非 TP 项目的外部调用(比如通过纯 HTTP POST 请求发送 JSON 数据的第三方客户端)。
下面是完整的服务侧挂载、逻辑解析与客户端请求的开发流程:
1. 订阅 Swoole 的 onRequest 事件
TP6 通过内置的事件系统管理 Swoole 周期。我们需要在 app/event.php 中订阅 swoole.request 事件:
php
return [
'bind' => [],
'listen' => [
// 绑定 Swoole 的全局 HTTP 请求事件
'swoole.request' => [
\app\listener\JsonRpcServerListener::class,
],
],
'subscribe' => [],
];
请谨慎使用此类代码。
2. 编写 JsonRPC 解析器监听类
创建事件监听类 app\listener\JsonRpcServerListener.php。在该类中获取客户端发来的原生 POST 载荷,按照 JSON-RPC 2.0 规范标准进行解析、路由分发和结果组装:
php
namespace app\listener;
use Swoole\Http\Request;
use Swoole\Http\Response;
class JsonRpcServerListener
{
/**
* 处理 Swoole 请求事件
*/
public function handle($event)
{
// $event 中包含 Swoole 的原始 Request 和 Response 对象
/** @var Request $request */
$request = $event->request;
/** @var Response $response */
$response = $event->response;
// 1. 设置响应头为标准的 JSON
$response->header('Content-Type', 'application/json; charset=utf-8');
// 2. 获取请求的原始 JSON 文本并解析
$rawContent = $request->rawContent();
$payload = json_decode($rawContent, true);
// 3. 基础规范校验
if (json_last_error() !== JSON_ERROR_NONE || !is_array($payload)) {
$this->sendError($response, -32700, 'Parse error', null);
return;
}
// 提取规范字段 (支持单条请求,如需支持批量请求可使用循环)
$jsonrpc = $payload['jsonrpc'] ?? '';
$method = $payload['method'] ?? '';
$params = $payload['params'] ?? [];
$id = $payload['id'] ?? null; // 为 null 时视为通知行为,原则上无回执
if ($jsonrpc !== '2.0' || empty($method)) {
$this->sendError($response, -32600, 'Invalid Request', $id);
return;
}
// 4. RPC 路由分发 (格式规范通常为:服务类/方法名,如 "user/getInfo")
$parts = explode('/', $method);
if (count($parts) !== 2) {
$this->sendError($response, -32601, 'Method not found', $id);
return;
}
[$serviceName, $actionName] = $parts;
// 映射到内部的具体业务类,假设放在 app\rpc 目录下
$className = "app\\rpc\\" . ucfirst($serviceName);
if (!class_exists($className) || !method_exists($className, $actionName)) {
$this->sendError($response, -32601, 'Method not found', $id);
return;
}
// 5. 执行业务方法并捕获可能出现的异常
try {
$serviceInstance = container($className); // 使用 TP 的容器实例化以支持依赖注入
// 执行业务类对应的方法 (由于 RPC 参数按数组传递,这里使用 call_user_func_array)
$result = call_user_func_array([$serviceInstance, $actionName], is_array($params) ? $params : [$params]);
// 6. 返回成功的规范响应
$response->end(json_encode([
'jsonrpc' => '2.0',
'result' => $result,
'id' => $id
]));
} catch (\Throwable $e) {
// 运行时业务代码引发的错误
$this->sendError($response, -32603, 'Internal error: ' . $e->getMessage(), $id);
}
}
/**
* 统一的错误回执响应
*/
private function sendError(Response $response, int $code, string $message, $id)
{
$response->end(json_encode([
'jsonrpc' => '2.0',
'error' => [
'code' => $code,
'message' => $message
],
'id' => $id
]));
}
}
请谨慎使用此类代码。
3. 创建具体的 RPC 业务类
在 app/rpc/User.php 中创建响应的具体业务类。这里不再受限于 TP 内置 RPC 的规则限制,编写普通的 PHP 类即可:
php
namespace app\rpc;
class User
{
/**
* 远程获取用户信息方法
* 客户端 params 传入的是键值对时,变量名对应参数名
*/
public function getInfo(int $uid)
{
if ($uid <= 0) {
throw new \Exception("UID错误");
}
return [
'uid' => $uid,
'name' => 'SwooleRPC用户',
'time' => date('Y-m-d H:i:s')
];
}
}
请谨慎使用此类代码。
4. 客户端如何请求调用?
现在服务端的 onRequest 已经变为一个标准无差异的 JsonRPC 高性能网关,调用端(无论是另一个 TP 项目、Go、Node.js 还是 Python)只需要发送一个标准的 HTTP POST 请求即可。
在客户端你可以直接用 curl 或 PHP 的 file_get_contents 发起调用验证:
php
$url = 'http://127.0.0.1:8000'; // 填写你服务端 think-swoole 运行的 HTTP 端口
$requestData = [
'jsonrpc' => '2.0',
'method' => 'user/getInfo', // 对应服务端绑定的 user 类与 getInfo 方法
'params' => ['uid' => 99], // 参数
'id' => time() // 消息流水号
];
$options = [
'http' => [
'header' => "Content-Type: application/json\r\n",
'method' => 'POST',
'content' => json_encode($requestData),
],
];
$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
echo $response;
// 输出结果类似: {"jsonrpc":"2.0","result":{"uid":99,"name":"SwooleRPC\u7528\u6237","time":"2026-06-09 17:31:00"},"id":1781016660}
请谨慎使用此类代码。