如何在 v4.x 中发布自定义的 JSON-RPC 服务

think-swoole v4.x 中,内置了对 RPC (远程过程调用) 的原生支持,默认基于 JSON-RPC 2.0 规范,并且全自动无缝融入了框架的容器与依赖注入机制。

下面为您完整演示如何在 v4.x发布自定义的 JSON-RPC 服务,以及如何从客户端(可以是另一个独立系统或测试脚本)进行高并发调用


一、 服务端配置与发布

1.1 开启 RPC 监听 (config/swoole.php)

首先,在配置文件中启用 rpc 服务,并定义您要发布(暴露给外部调用)的接口和类。

php

return [
    // 其他基础配置保持不变...
    
    'rpc' => [
        'enable'     => true,
        'port'       => 9000, // RPC 服务单独监听的端口
        // 允许公开访问的业务接口列表
        'services'   => [
            // 格式:'客户端调用的别名' => 具体的类名
            'UserService' => \app\rpc\UserService::class,
        ],
    ],
];

请谨慎使用此类代码。

1.2 创建服务接口与实现类

为了规范化管理,建议先定义一个接口(Interface),再编写实现类(Class)。

  • 步骤 1:定义公共接口 (app/rpc/interface/UserInterface.php)

    php

    namespace app\rpc\interface;
    
    interface UserInterface
    {
        public function getUserInfo(int $id): array;
        public function register(string $username, string $password): bool;
    }
    

    请谨慎使用此类代码。

  • 步骤 2:编写具体的业务实现类 (app/rpc/UserService.php)

    php

    namespace app\rpc;
    
    use app\rpc\interface\UserInterface;
    use think\facade\Db; // 支持在 RPC 服务中直接使用 TP 数据库连接池
    
    class UserService implements UserInterface
    {
        public function getUserInfo(int $id): array
        {
            // 在常驻内存环境下,此处的数据库查询会自动走 think-swoole 的连接池
            // $user = Db::name('user')->find($id); 
    
            // 模拟测试数据
            return [
                'id'       => $id,
                'name'     => '张三',
                'role'     => '管理员',
                'time'     => date('Y-m-d H:i:s')
            ];
        }
    
        public function register(string $username, string $password): bool
        {
            if (empty($username) || empty($password)) {
                return false;
            }
            return true;
        }
    }
    

    请谨慎使用此类代码。

启动/重启 Swoole 服务:配置好后,运行 php think swoole restart。此时,Swoole 除了在 HTTP 端口监听网页请求外,还会在 9000 端口监听 JSON-RPC 请求。


二、 客户端调用自定义 RPC 服务

由于它是标准的 JSON-RPC 2.0 协议,您有两种主要方式来调用它:

2.1 方式 A:在另一个 ThinkPHP 客户端中调用(原生支持)

如果您是在另一个 ThinkPHP 项目中去远程连接这个服务,可以直接在客户端的 config/swoole.php 中配置并使用依赖注入。

  • 客户端配置 (config/swoole.php)

    php

    'rpc' => [
        'enable'   => false, // 客户端本身不需要开启 RPC 监听
        'client'   => [
            // 定义连接到远程的哪个服务
            'UserService' => [
                'host' => '127.0.0.1', // 远程服务端 IP
                'port' => 9000,
            ],
        ],
    ],
    

    请谨慎使用此类代码。

  • 客户端代码中直接调用

    php

    namespace app\controller;
    
    use app\rpc\interface\UserInterface;
    
    class Index
    {
        // 框架会自动通过 RPC 客户端代理注入该接口
        public function test(UserInterface $userService)
        {
            // 像本地方法一样直接调用,底层自动走 RPC 序列化通信
            $result = $userService->getUserInfo(1001);
    
            return json($result);
        }
    }
    

    请谨慎使用此类代码。

2.2 方式 B:跨语言/纯 PHP 原生 Curl 调用(轻量、无框架限制)

如果你的客户端是外部系统(如纯 PHP 脚本、Go、Python 甚至是前端),可以通过标准的 JSON-RPC 格式直接发送 POST 请求:

php

// 构造标准的 JSON-RPC 2.0 请求载荷
$payload = [
    'jsonrpc' => '2.0',
    'method'  => 'UserService.getUserInfo', // 格式:服务别名.方法名
    'params'  =>,                    // 对应的参数列表
    'id'      => uniqid(),                  // 消息唯一标识
];

$ch = curl_init('http://127.0.0.1:9000');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);

$response = curl_exec($ch);
curl_close($ch);

// 打印服务端返回的 JSON 数据
print_異常($response);
/* 
返回示例:
{
    "jsonrpc": "2.0",
    "result": {"id":1001,"name":"张三","role":"管理员","time":"2026-06-14 22:09:12"},
    "id": "6431a7bc..."
}
*/

请谨慎使用此类代码。


三、 RPC 服务高级避坑指南

  1. 参数类型严格匹配:在协程 RPC 下,方法的参数类型约束(如 int $id)非常严格。客户端传递参数时必须保证类型完全一致。例如,如果客户端在 HTTP 请求中拿到了字符串类型的 '1001' 传给 RPC,服务端如果限定了 int 可能会触发类型异常报错。

  2. 异常捕获(Exception):RPC 服务端代码中如果发生未捕获的报错,think-swoole 会将其包装为 JSON-RPC 标准错误响应(error 字段)返回给客户端,而不会导致 Swoole 主进程崩溃,但建议在服务端内部做好 try-catch 并返回自定义的状态码。

  3. 常驻内存隔离:在 UserService 内部,千万不要使用类成员变量(如 $this->userId)去存储当前请求的用户状态,因为这个类实例在内存中是复用的,多线程/多协程并发时会导致数据错乱。

如果您在开发中觉得每次都先定义一个 Interface(接口)太繁琐,完全可以跳过接口定义。在 think-swoole v4.x 中,支持直接将一个普通的业务类(Class)作为 RPC 服务发布和调用。

不使用接口时的服务端配置客户端调用以及跨语言调用的完整改造方案如下:


一、 服务端:直接发布普通的类(Class)

1.1 编写无接口的业务类 (app/rpc/UserService.php)

直接写一个普通类即可,无需 implements 任何接口:

php

namespace app\rpc;

class UserService
{
    // 普通方法,直接编写业务
    public function getUserInfo(int $id): array
    {
        return [
            'id'   => $id,
            'name' => '张三(无接口模式)',
            'time' => date('Y-m-d H:i:s')
        ];
    }
}

请谨慎使用此类代码。

1.2 在 config/swoole.php 中直接注册此类

php

'rpc' => [
    'enable'     => true,
    'port'       => 9000,
    'services'   => [
        // 别名 => 具体的业务类
        'UserService' => \app\rpc\UserService::class,
    ],
],

请谨慎使用此类代码。

配置完成后,记得重启服务:php think swoole restart


二、 客户端:两种无需接口的调用方式

在没有公共接口(Interface)的情况下,如果直接在控制器里通过类型提示注入(如 public function test(UserInterface $user))会因为找不到接口类而报错。此时可以通过以下两种方式调用:

方式 A:使用容器或 app() 动态获取客户端实例(最推荐)

ThinkPHP 的 think-swoole 扩展在底层会将注册的 RPC 客户端绑定到容器中。您无需引入任何接口文件,直接通过服务别名即可动态获取远程调用代理对象。

  • 客户端配置 (config/swoole.php)

    php

    'rpc' => [
        'enable'   => false,
        'client'   => [
            'UserService' => [
                'host' => '127.0.0.1',
                'port' => 9000,
            ],
        ],
    ],
    

    请谨慎使用此类代码。

  • 业务代码调用

    php

    namespace app\controller;
    
    class Index
    {
        public function test()
        {
            // 通过 app() 容器直接获取远程 RPC 客户端实例(传入配置的别名)
            $userService = app('UserService');
    
            // 直接像本地对象一样调用方法
            $result = $userService->getUserInfo(2026);
    
            return json($result);
        }
    }
    

    请谨慎使用此类代码。

方式 B:纯 PHP 数组/网络请求调用(万能调用法)

由于不依赖接口,您还可以直接使用标准的 JSON-RPC 2.0 协议发送 POST 请求。这种方式不限制任何框架,纯 PHP、Go、Python 均可通用。

php

$payload = [
    'jsonrpc' => '2.0',
    'method'  => 'UserService.getUserInfo', // 格式:服务别名.方法名
    'params'  =>,                     // 按顺序传入参数
    'id'      => uniqid(),
];

$ch = curl_init('http://127.0.0.1:9000');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);

$response = curl_exec($ch);
curl_close($ch);

print_r(json_decode($response, true));

请谨慎使用此类代码。


💡 无接口模式的注意事项

  1. IDE 代码提示减弱:不使用接口时,客户端在使用 $userService->getUserInfo() 时,编辑器(如 PhpStorm)可能无法自动联想出该方法和参数类型。如果您需要代码提示,可以在客户端上方加一行标准注释:

    php

    /** @var \app\rpc\UserService $userService */
    $userService = app('UserService');
    

    请谨慎使用此类代码。

    (前提是客户端本地也有一份一模一样的 UserService 类文件用于代码识别)

  2. 方法名和参数顺序:没有了接口的强约束,客户端在调用时,必须严格保证传入的参数顺序与服务端类中定义的方法参数顺序完全一致。