针对前文提到的 方案二(基于 onRequest 事件挂载的标准 JsonRPC 2.0 协议),客户端如果是 TP(ThinkPHP)项目,由于它走的是标准的 HTTP(S) 协议,有以下两种非常实用的调用实现方式。
写法 A:使用原生 GuzzleHTTP 客户端(高并发、支持连接池)
在 TP 项目中,推荐引入 guzzlehttp/guzzle 组件(TP6 默认通常已依赖,或通过 composer require guzzlehttp/guzzle 安装)。它完美支持 Swoole 环境下的协程并发展开。
1. 封装一个基础 JsonRpcClient 客户端类
在客户端项目创建 app\rpc\client\JsonRpcClient.php:
php
namespace app\rpc\client;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
class JsonRpcClient
{
protected $client;
protected $url = 'http://127.0.0.1:8000'; // 服务端 think-swoole 运行的 HTTP 端口
public function __construct()
{
// 初始化 Guzzle 客户端
$this->client = new Client([
'timeout' => 5.0, // 设置超时时间
'headers' => [
'Content-Type' => 'application/json',
]
]);
}
/**
* 发送标准的 JsonRPC 2.0 请求
*/
public function call(string $method, array $params = [])
{
$id = time() . rand(1000, 9999); // 生成唯一的请求流水号
$payload = [
'jsonrpc' => '2.0',
'method' => $method, // 对应服务端的 "类名/方法名",例如 "user/getInfo"
'params' => $params,
'id' => $id,
];
try {
$response = $this->client->post($this->url, [
'json' => $payload
]);
$body = json_decode($response->getBody()->getContents(), true);
// 1. 判断是否符合 JsonRPC 2.0 错误规范
if (isset($body['error'])) {
throw new \Exception("RPC Error [{$body['error']['code']}]: {$body['error']['message']}");
}
// 2. 返回正常结果
return $body['result'] ?? null;
} catch (GuzzleException $e) {
throw new \Exception("RPC 网络请求失败: " . $e->getMessage());
}
}
}
请谨慎使用此类代码。
2. 在控制器中进行调用
php
namespace app\controller;
use app\rpc\client\JsonRpcClient;
class UserController
{
public function info(JsonRpcClient $rpcClient)
{
// 传入 "服务类/方法名"
$userInfo = $rpcClient->call('user/getInfo', ['uid' => 99]);
return json($userInfo);
}
}
请谨慎使用此类代码。
写法 B:面向对象代理写法(优雅、带 IDE 代码提示)
为了达到前述“像调用本地方法一样”的流畅体验,我们同样可以在 JsonRPC 之上套一层代理,自动将方法名转换为 user/getInfo 格式。
1. 编写 JsonRPC 代理基类
创建 app\rpc\client\JsonRpcProxy.php:
php
namespace app\rpc\client;
abstract class JsonRpcProxy
{
protected $serviceName = ''; // 由子类指定,例如 'user'
protected $rpcClient;
public function __construct(JsonRpcClient $rpcClient)
{
$this->rpcClient = $rpcClient;
}
/**
* 拦截本地调用,组装为标准的 JsonRPC method
*/
public function __call($method, $arguments)
{
// 拼接成服务端所需的 "user/getInfo" 格式
$rpcMethod = $this->serviceName . '/' . $method;
// JsonRPC 参数通常为关联数组(键值对)
// 如果客户端传参形如 ->getInfo(['uid' => 99]),直接取第一个参数
$params = isset($arguments[0]) && is_array($arguments[0]) ? $arguments[0] : $arguments;
return $this->rpcClient->call($rpcMethod, $params);
}
}
请谨慎使用此类代码。
2. 派生具体的业务服务类
创建 app\rpc\client\UserService.php:
php
namespace app\rpc\client;
/**
* 用户中心 JsonRPC 远程客户端
* @method array getInfo(array $params) <-- 编写提示注解
*/
class UserService extends JsonRpcProxy
{
protected $serviceName = 'user'; // 对应服务端 app\rpc\User 类
}
请谨慎使用此类代码。
3. 最终在控制器中优雅调用
此时,团队其他开发人员在客户端写代码时,完全不需要关心底层的 JsonRPC 协议格式:
php
namespace app\controller;
use app\rpc\client\UserService;
class Index
{
public function test(UserService $userService)
{
// 原生调用感,参数以键值对(数组)方式传递给服务端的 getInfo(int $uid)
$result = $userService->getInfo(['uid' => 101]);
return json($result);
}
}
请谨慎使用此类代码。
💡 JsonRPC 模式的高级拓展提示
批量请求 (Batch Request):标准的 JsonRPC 2.0 支持一次性发送一个数组(包含多个请求包)。如果客户端需要同时获取用户信息、订单信息,可以扩展
JsonRpcClient的call方法,使其支持发送二维数组,从而通过一次网络 I/O 拿到所有数据,最大化发挥 Swoole 的并发性能。跨语言联通:由于此方案基于标准的 JsonRPC 协议,该客户端不仅能调 TP 服务端,还可以无缝调用 Python (jsonrpcserver)、Go (net/rpc) 或 Node.js 编写的微服务。