在 ThinkPHP 6.x / 8.x 结合 think-swoole 扩展的环境下,通过自定义配置文件直接增加 listen 端口并绑定原生回调是完全可行的。
think-swoole 的底层是基于 Swoole 的,它在框架初始化时会读取 config/swoole.php。我们可以利用 Swoole 的 server 或者是 think-swoole 提供的事件系统,在主服务启动前(custom_init 或 ServerManager)动态添加监听端口,并绑定标准的 JSON-RPC 原生回调。
以下是为您量身定制的无缝接入适配方案,无需修改框架源码,直接配置即可投入生产:
🛠️ 生产级落地步骤
步骤一:创建 JSON-RPC 协议处理器
我们在 app/rpc 目录下(若不存在可自行创建)新建一个处理器类,专门负责解析标准 JSON-RPC 2.0 协议、调用 ThinkPHP 内部控制器/服务,并返回标准格式。
创建文件:app/rpc/JsonRpcHandler.php
php
<?php
namespace app\rpc;
use Swoole\Server;
use think\Container;
use think\App;
class JsonRpcHandler
{
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
/**
* 接管监听端口的 Receive 回调
*/
public function onReceive(Server $server, int $fd, int $reactorId, string $data)
{
// 1. 去除包尾换行符(配合 EOF 拆包)
$payload = rtrim($data, "\r\n");
$request = json_decode($payload, true);
// 2. 验证是否符合标准 JSON-RPC 2.0 规范
if (!$request || !isset($request['jsonrpc']) || $request['jsonrpc'] !== '2.0' || !isset($request['method'])) {
$this->sendError($server, $fd, -32700, "Parse error or invalid request");
return;
}
$method = $request['method'];
$params = $request['params'] ?? [];
$id = $request['id'] ?? null;
try {
// 3. 将 JSON-RPC 的 method(例如 "user.info" 或 "UserService@getInfo")映射到框架内部
$result = $this->dispatchToContainer($method, $params);
// 4. 返回标准成功的格式
$response = [
"jsonrpc" => "2.0",
"result" => $result,
"id" => $id
];
$server->send($fd, json_encode($response) . "\r\n");
} catch (\Throwable $e) {
// 5. 捕获内部异常,转换为标准的 JSON-RPC 错误码
$code = $e->getCode() ?: -32603; // 默认 Internal Error
$this->sendError($server, $fd, $code, $e->getMessage(), $id);
}
}
/**
* 容器分发:将 RPC 请求路由到 ThinkPHP 的 Service、Controller 或应用容器中
*/
protected function dispatchToContainer(string $method, array $params)
{
// 示例规则:将 "user.getInfo" 解析为调用 app\rpc\service\UserService 类的 getInfo 方法
// 您可以根据自己的命名空间规则自行调整解析逻辑
if (strpos($method, '.') !== false) {
list($serviceName, $action) = explode('.', $method);
} else {
throw new \Exception("Method not found (Invalid format)", -32601);
}
$className = "app\\rpc\\service\\" . ucfirst($serviceName) . "Service";
if (!class_exists($className)) {
throw new \Exception("Service class not found", -32601);
}
// 从 ThinkPHP 容器中获取实例(保证依赖注入正常工作)
$instance = Container::getInstance()->make($className);
if (!method_exists($instance, $action)) {
throw new \Exception("Method not found in service", -32601);
}
// 执行业务方法(利用 invokeMethod 自动实现参数依赖注入)
return Container::getInstance()->invokeMethod([$instance, $action], $params);
}
/**
* 错误回包封装
*/
protected function sendError(Server $server, int $fd, int $code, string $message, $id = null)
{
$response = [
"jsonrpc" => "2.0",
"error" => [
"code" => $code,
"message" => $message
],
"id" => $id
];
$server->send($fd, json_encode($response) . "\r\n");
}
}
请谨慎使用此类代码。
步骤二:通过 think-swoole 事件动态绑定多端口
think-swoole 提供了 swoole.init 事件,该事件在 Swoole Server 创建后、启动(start)前触发,是注入多端口(listen)和原生回调的绝佳时机。
创建或修改文件:app/listener/SwooleInitListener.php
php
<?php
namespace app\listener;
use Swoole\Server;
use think\App;
use app\rpc\JsonRpcHandler;
class SwooleInitListener
{
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
/**
* 监听 swoole.init 事件
*/
public function handle(Server $server)
{
// 1. 通过主服务动态 listen 新的 TCP 端口(例如 9502)
// 可以根据需要将配置提到 config/swoole.php 中
$jsonRpcPort = $server->listen("0.0.0.0", 9502, SWOOLE_SOCK_TCP);
if ($jsonRpcPort === false) {
// 端口监听失败处理
return;
}
// 2. 为该端口设置独立的协议分包参数(开启 EOF 检测防粘包)
$jsonRpcPort->set([
'open_eof_check' => true,
'package_eof' => "\r\n", // 约定每条 JSON 数据以换行符结尾
'package_max_length' => 1024 * 1024 * 2, // 最大 2MB 包
]);
// 3. 从 ThinkPHP 容器实例化刚才创建的处理器
/** @var JsonRpcHandler $handler */
$handler = $this->app->make(JsonRpcHandler::class);
// 4. 绑定该端口专用的原生回调
$jsonRpcPort->on('Receive', [$handler, 'onReceive']);
}
}
请谨慎使用此类代码。
步骤三:注册事件监听
打开 ThinkPHP 全局或应用的事件配置文件 app/event.php,将编写好的监听器注册到 think-swoole 的初始化事件中:
php
return [
'bind' => [],
'listen' => [
// 注册到 think-swoole 的服务器初始化事件
'swoole.init' => [
\app\listener\SwooleInitListener::class,
],
],
'subscribe' => [],
];
请谨慎使用此类代码。
步骤四:编写您的内部业务 RPC 服务
现在可以按照在 JsonRpcHandler 里面定义的路由规则,编写实际承载业务的服务类了。
创建文件:app/rpc/service/UserService.php
php
<?php
namespace app\rpc\service;
class UserService
{
/**
* 测试 RPC 方法
* 请求中的 params 数组若包含键名(如 ['uid' => 1001]),
* ThinkPHP 的容器会自动将其赋给同名形参 $uid
*/
public function getInfo(int $uid)
{
if ($uid <= 0) {
// 自定义业务异常,会被 handler 捕获并转为 JSON-RPC 格式
throw new \Exception("Invalid user ID", -32602); // -32602 Invalid params
}
return [
'uid' => $uid,
'nickname' => 'ThinkSwoole开发者',
'role' => 'Admin',
'time' => date('Y-m-d H:i:s')
];
}
}
请谨慎使用此类代码。
🚀 启动与联调测试
使用命令行正常启动你的
think-swoole服务:bash
php think swoole start请谨慎使用此类代码。
原有的 HTTP 端口(如 9501)和官方原生功能依然不受影响。
打开任意 TCP 调试工具(如
nc、Telnet)或使用客户端向 9502 端口发送标准的 JSON-RPC 2.0 数据包。记得在数据包末尾加上回车换行(\r\n):请求报文(Payload):json
{"jsonrpc": "2.0", "method": "user.getInfo", "params": {"uid": 888}, "id": "req-001"}\r\n请谨慎使用此类代码。
标准响应报文:
json
{"jsonrpc":"2.0","result":{"uid":888,"nickname":"ThinkSwoole\u5f00\u53d1\u8005","role":"Admin","time":"2026-06-12 18:42:00"},"id":"req-001"}\r\n请谨慎使用此类代码。
💡 针对 Think-Swoole 的架构优化建议
协程上下文清理:由于是在多端口的原生
Receive回调里直接调用了容器逻辑,如果业务中使用了think\facade\Context或协程相关的单例,为了防止多连接间的数据串户,建议在onReceive的try...catch...finally块中显式执行一下上下文的clear()动作。自定义路由字典:当前 Demo 采用的是传统的
Class.Method字符串切割。如果项目庞大,可以在JsonRpcHandler中维护一个完全自定义的Map字典,将前端暴露的method字符串与内部真实的控制器进行硬编码或注解映射,从而保障内部代码结构的安全性。