作者 pengjch

dev: swoole rpc

  1 +## 简介
  2 +AukeySwrpc是一个基于swoole开发的高性能rpc包,AukeySwrpc提供了注册发现,链路追踪,中间件等等功能,可以很容易集成到第三方框架,如laravel,yii等等。
  3 +
  4 +
  5 +
  6 +## 功能
  7 +
  8 +- 支持多进程模式或协程模式
  9 +- 支持同步,异步调用
  10 +- 支持自定义中间件
  11 +- 支持服务端按类提供对外服务
  12 +- 支持链路追踪
  13 +- 支持注册服务发现
  14 +- 支持客户端负载均衡,包含随机,权重两种模式
  15 +- 支持自定义日志,包协议,序列化方式等等
  16 +
  17 +
  18 +
  19 +## 安装
  20 +
  21 +```bash
  22 +php composer.phar require wuzhc/swprc ~1.0 -vvv
  23 +```
  24 +
  25 +
  26 +
  27 +## 快速体验
  28 +
  29 +假设我们User和School两个模块,为了获得用户学校名称,我们需要在User模块发起rpc请求调用School模块的服务。
  30 +
  31 +### School模块
  32 +
  33 +```php
  34 +<?php
  35 +use AukeySwrpc\LogicService;
  36 +class SchoolService extends LogicService
  37 +{
  38 + public function getUserSchool($userID) {
  39 + $name = $userID == 123 ? '火星' : '水星';
  40 + return $name.'学校';
  41 + }
  42 +}
  43 +```
  44 +
  45 +School模块将作为rpc服务端,对外提供服务,启动如下:
  46 +
  47 +```php
  48 +<?php
  49 +namespace AukeySwrpcTests;
  50 +use AukeySwrpc\Server;
  51 +
  52 +$basePath = dirname(dirname(__FILE__));
  53 +require_once $basePath . "/vendor/autoload.php";
  54 +
  55 +$options = [
  56 + 'enable_coroutine' => true,
  57 + 'pid_file' => __DIR__ . '/AukeySwrpc.pid',
  58 +];
  59 +$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);
  60 +$server->addService(\AukeySwrpcTests\services\SchoolService::class);
  61 +$server->start();
  62 +```
  63 +
  64 +将SchoolService添加到server,server会自动检索类中所有可用的public方法。
  65 +
  66 +![1615562843265](https://segmentfault.com/img/bVcPwbR)
  67 +
  68 +
  69 +
  70 +### User模块
  71 +
  72 +User模块作为客户端,调用School模块服务如下
  73 +
  74 +```php
  75 +<?php
  76 +namespace AukeySwrpcTests;
  77 +use AukeySwrpc\Request;
  78 +use AukeySwrpc\LogicService;
  79 +use AukeySwrpc\Client;
  80 +
  81 +class UserService extends LogicService
  82 +{
  83 + public function getUserSchoolName()
  84 + {
  85 + $userID = 123;
  86 + $module = 'School_Module'; //请求目标模块名称,需要和服务端定义的一致
  87 + $client = Client::create($module, '127.0.0.1', 9501);
  88 + return $client->send(Request::create('\AukeySwrpcTests\services\SchoolService_getUserSchool', [$userID]));
  89 + }
  90 +}
  91 +
  92 +//调用
  93 +echo UserService::factory()->getUserSchoolName();
  94 +```
  95 +注意:
  96 +- Request.method 为服务类命名 + 下划线 + 方法名,例如上面的`\AukeySwrpcTests\services\SchoolService_getUserSchool`,如果服务类有命名空间,记得一定要带上命名空间
  97 +
  98 +
  99 +
  100 +## 多进程和协程模式
  101 +
  102 +多进程或协程模式需要和swoole配置一致,具体参考swoole配置
  103 +
  104 +### 多进程模式
  105 +
  106 +创建10进程来处理请求
  107 +
  108 +```php
  109 +$options = [
  110 + 'worker_num' => 10
  111 + 'pid_file' => __DIR__ . '/AukeySwrpc.pid',
  112 +];
  113 +$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);
  114 +```
  115 +
  116 +### 协程模式
  117 +
  118 +目前AukeySwrpc协程模式是运行在单进程的
  119 +
  120 +```php
  121 +$options = [
  122 + 'enable_coroutine' => true,
  123 + 'pid_file' => __DIR__ . '/AukeySwrpc.pid',
  124 +];
  125 +$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);
  126 +```
  127 +
  128 +
  129 +
  130 +## 同步调用和异步调用
  131 +
  132 +在客户端发起同步调用,客户端会一直等待服务端返回结果
  133 +
  134 +```php
  135 +$client = \AukeySwrpc\Client::create($module, '127.0.0.1', 9501);
  136 +return $client->send(SyncRequest::create('SchoolService_getUserSchool', [$userID]));
  137 +```
  138 +
  139 +在客户端发起异步调用,客户端会立马得到响应结果,请求将被swoole的task进程处理
  140 +
  141 +```php
  142 +$client = \AukeySwrpc\Client::create($module, '127.0.0.1', 9501);
  143 +return $client->send(AsyncRequest::create('SchoolService_getUserSchool', [$userID]));
  144 +```
  145 +
  146 +
  147 +
  148 +## 自定义中间件
  149 +
  150 +中间件允许程序可以对请求进行前置操作和后置操作,底层使用了责任链设计模式,所以为了执行下一个中间件,必须返回`$next($request)`,如果想提前返回,则返回结果必须是`AukeySwrpc\Response`类型
  151 +
  152 +```php
  153 +//中间件除了用匿名函数定义,还可以用实现AukeySwrpc\Middlewares\MiddlewareInterface接口的类
  154 +$middleware = function (\AukeySwrpc\Request $request, Closure $next) {
  155 + $start = microtime(true); //前置操作,记录请求开始时间
  156 + $result = $next($request);
  157 + echo '耗时:'.(microtime(true) - $start).PHP_EOL; //后置操作,记录请求结束时间,从而计算请求耗时
  158 + return $result; //继续下个中间件的处理
  159 +};
  160 +$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);
  161 +$server->addService(SchoolService::class);
  162 +$server->addMiddleware($middleware); //添加中间件
  163 +$server->start();
  164 +```
  165 +如果要提前中止中间件,可以提前在匿名函数或类方法中返回\AukeySwrpc\Response对象,如下
  166 +```php
  167 +$middleware = function (\AukeySwrpc\Request $request, Closure $next) {
  168 + if (empty($request->getParams())) {
  169 + return \AukeySwrpc\Response::error('参数不能为空'); //提前返回,必须是Response类型
  170 + }
  171 + return $next($request);
  172 +};
  173 +```
  174 +
  175 +
  176 +
  177 +## 服务端按类提供对外服务
  178 +
  179 +从上面的例子中,我们把SchoolService整个类添加的server中,这样server就能对外提供SchoolService类所有public方法的功能。
  180 +
  181 +```php
  182 +$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);
  183 +$server->addService(SchoolService::class); //提供SchoolService所有public方法功能
  184 +$server->addService(AreaService::class); //提供AreaService所有public方法功能
  185 +$server->start();
  186 +```
  187 +客户端使用参考上面的快速体验
  188 +
  189 +
  190 +
  191 +## 注册服务发现
  192 +
  193 +如果服务端启动的时候有设置注册中心,则启动成功会自动向注册中心注册服务端地址。目前AukeySwrpc提供了`Consul`作为注册中心,使用如下
  194 +
  195 +```php
  196 +$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);
  197 +$server->addRegister(new Consul());
  198 +$server->addService(SchoolService::class);
  199 +$server->start();
  200 +```
  201 +
  202 +如上,使用Consul作为服务的注册中心,通过`http://127.0.0.1:8500`可以查看注册信息,如果想用etcd等其他注册中心,只要实现`AukeySwrpc\Middlewares\RegisterInterface`接口即可,然后在通过`$server->addRegister()`添加到server
  203 +
  204 +![1615562878292](https://segmentfault.com/img/bVcPwbR)
  205 +
  206 +![1615562927956](https://segmentfault.com/img/bVcPwb4)
  207 +
  208 +![1615562975815](https://segmentfault.com/img/bVcPwb5)
  209 +
  210 +
  211 +
  212 +## 客户端负载均衡
  213 +
  214 +如果服务端启动多个节点,例如School模块启动3个节点,并且注册到了注册中心,那么我们可以从注册中心获取所有服务端节点信息,然后做一些策略处理。
  215 +
  216 +```php
  217 +$register = new Consul();
  218 +$client = \AukeySwrpc\Client::createBalancer('School_Module', $register, \AukeySwrpc\Client::STRATEGY_WEIGHT);
  219 +$result = $client->send(Request::create('SchoolService_getUserSchool', [$userID]);
  220 +```
  221 +
  222 +目前AukeySwrpc提供两种简单策略模式,`\AukeySwrpc\Client::STRATEGY_WEIGHT权重模式`,`\AukeySwrpc\Client::STRATEGY_RANDOM`随机模式
  223 +
  224 +
  225 +
  226 +## 链路追踪
  227 +
  228 +当我们的服务非常多并且需要互相调用时候,如果其中某个调用失败,会导致我们得不到我们想要的结果,而要调试出是哪个环节出了问题也比较麻烦,可能你需要登录每台机器看下有没有错误日志,或者看返回的错误信息是哪个服务提供的。链路追踪记录了整个调用链过程,如果某个环节出错,我们可以快速从调用链得到调用中断地方。
  229 +
  230 +```php
  231 +class UserService extends LogicService
  232 +{
  233 + public function getUserSchoolName()
  234 + {
  235 + $userID = 123;
  236 + $module = 'School_Module'; //请求目标模块名称,需要和服务端定义的一致
  237 + $client = Client::create($module, '127.0.0.1', 9501);
  238 + return $client->send(Request::create('SchoolService_getUserSchool', [$userID], $this->getTracerContext(__FUNCTION__))); //getTracerContext()用于提供追踪上下文
  239 + }
  240 +}
  241 +
  242 +$users = UserService::factory()
  243 + ->setModule('User_Module') //当前模块,用于调用链的起点
  244 + ->setTracerUrl('http://127.0.0.1:9411/api/v2/spans') //zipkin链路追踪地址
  245 + ->getUserSchoolName();
  246 +```
  247 +
  248 +![1615563070157](https://segmentfault.com/img/bVcPwb6)
  249 +
  250 +如图,User_Module调用Class_Module,Class_Module又去调用School_Module
  251 +
  252 +![1615563170709](https://segmentfault.com/img/bVcPwb8)
  253 +
  254 +每个调用还记录响应结果
  255 +
  256 +
  257 +
  258 +
  259 +## 自定义日志处理器
  260 +默认使用`Monolog/Logger`作为日志处理器,日志信息会输出到控制台。可根据自己需求覆盖默认处理器,只要日志类
  261 +实现`Psr\Log\LoggerInterface`即可
  262 +```php
  263 +use AukeySwrpc\Server;
  264 +use Monolog\Handler\StreamHandler;
  265 +use Monolog\Logger;
  266 +
  267 +$logger = new Logger('AukeySwrpc');
  268 +$logger->pushHandler(new StreamHandler(fopen('xxxx.log','w+'), Logger::DEBUG));
  269 +
  270 +$server = new Server('127.0.0.1', 9501, ['enable_coroutine'=>true]);
  271 +$server->addService(UserService::class);
  272 +$server->addLogger($logger); //覆盖默认日志处理器
  273 +$server->start();
  274 +```
  275 +
  276 +
  277 +
  278 +## 序列化方式
  279 +
  280 +默认使用固定头+包体来解决**tcp粘包**问题,默认配置为`'package_length_type' => 'N'`,`'package_body_offset' => 4`
  281 +默认序列化数据会使用`serialize()`,如果swoole版本在4.5以上的自动使用swoole_substr_unserialize(),可以实现的类来覆盖默认配置,只要实现`src/Packer/PackerInterface`即可,注意服务端和客户端需要使用一样的协议,否则解析不了。
  282 +
  283 +```php
  284 +use AukeySwrpc\Server;
  285 +
  286 +$packer = new \AukeySwrpc\Packer\SerializeLengthPacker();
  287 +$server = new Server('127.0.0.1', 9501, ['enable_coroutine'=>true]);
  288 +$server->addService(UserService::class);
  289 +$server->addPacker($packer); //覆盖默认值
  290 +```
  291 +
  292 +
  293 +
  294 +## 安全证书配置
  295 +
  296 +参考:<https://wiki.swoole.com/#/server/setting?id=ssl_cert_file>
  297 +
  298 +### 服务端
  299 +
  300 +```php
  301 +$options = [
  302 + 'ssl_cert_file' => __DIR__.'/config/ssl.crt',
  303 + 'ssl_key_file' => __DIR__.'/config/ssl.key',
  304 + 'pid_file' => __DIR__ . '/AukeySwrpc.pid',
  305 +];
  306 +$server = new Server('School_Module', '127.0.0.1', 9501, $options, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
  307 +$server->addService(SchoolService::class);
  308 +$server->start();
  309 +```
  310 +
  311 +注意:
  312 +
  313 +- 文件必须为 `PEM` 格式,不支持 `DER` 格式,可使用 `openssl` 工具进行转换
  314 +
  315 +
  316 +
  317 +## 测试
  318 +
  319 +使用phpuint实现的简单测试案例,配置文件`phpunit.xml`,根据你的服务器配置ip地址
  320 +
  321 +```bash
  322 +php phpunit.phar tests --debug
  323 +```
  324 +
  325 +![1615602809212](https://segmentfault.com/img/bVcPwcb)
  326 +
  327 +### phpunit 测试报告
  328 +
  329 +```
  330 +Client (AukeySwrpcTests\Client)
  331 + [x] Client connect
  332 + [x] Client sync request
  333 + [x] Client async request
  334 +
  335 +Packer (AukeySwrpcTests\Packer)
  336 + [x] Serialize length pack
  337 + [x] Serialize lenght unpack
  338 + [x] Serialize eof pack
  339 + [x] Serialize eof unpack
  340 +
  341 +Server (AukeySwrpcTests\Server)
  342 + [x] Server register to consul
  343 + [x] Server unregister from consul
  344 + [x] Server add service
  345 + [x] Server add middleware
  346 +```
  1 +{
  2 + "name": "aukey/aukeySwrpc",
  3 + "description": "基于swoole的rpc库",
  4 + "license": "MIT",
  5 + "authors": [
  6 + {
  7 + "name": "pengjianchao",
  8 + "email": "pengjianchao@aukeys.com"
  9 + }
  10 + ],
  11 + "require": {
  12 + "php": ">=7.3",
  13 + "psr/log": "^1.0",
  14 + "monolog/monolog": "^2.2",
  15 + "sensiolabs/consul-php-sdk": "~4.0",
  16 + "openzipkin/zipkin": "~2.0"
  17 + },
  18 + "require-dev": {
  19 + "phpunit/phpunit": "~9.5"
  20 + },
  21 + "autoload": {
  22 + "psr-4": {
  23 + "AukeySwrpc\\": "src/",
  24 + "AukeySwrpcTests\\": "tests/"
  25 + },
  26 + "classmap": [
  27 + "src/"
  28 + ]
  29 + }
  30 +}
此 diff 太大无法显示。
  1 +<?xml version="1.0"?>
  2 +<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
  3 + <coverage/>
  4 + <php>
  5 + <env name="CONSUL_HOST" value="127.0.0.1"/>
  6 + <env name="CONSUL_PORT" value="8500"/>
  7 + <env name="RPC_SERVER_HOST" value="127.0.0.1"/>
  8 + <env name="RPC_SERVER_PORT" value="19501"/>
  9 + </php>
  10 + <logging>
  11 + <testdoxText outputFile="./AukeySwrpc_test_report.log"/>
  12 + </logging>
  13 +</phpunit>
  1 +<?php
  2 +
  3 +namespace AukeySwrpc;
  4 +
  5 +use Illuminate\Support\ServiceProvider;
  6 +
  7 +class AukeyAukeySwrpcProvider extends ServiceProvider
  8 +{
  9 + public function boot()
  10 + {
  11 + $this->loadMigrationsFrom(__DIR__ . '/database/migrations');
  12 + if ($this->app->runningInConsole()) {
  13 + $this->commands([
  14 + \AukeyDataCenter\Commands\CreateBaseCode::class,
  15 + \AukeyDataCenter\Commands\SheinReturnOrderCom::class,
  16 + \AukeyDataCenter\Commands\SheinProductCom::class,
  17 + \AukeyDataCenter\Commands\SheinBrandCom::class,
  18 + ]);
  19 + }
  20 + }
  21 +
  22 + public function register()
  23 + {
  24 + $provides = [
  25 + 'AukeyDataCenter\Http\RouteServiceProvider',
  26 + ];
  27 + foreach ($provides as $provider) {
  28 + $this->app->register($provider);
  29 + }
  30 + }
  31 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc;
  4 +
  5 +
  6 +use Swoole\Client as SwClient;
  7 +use AukeySwrpc\Exceptions\RpcException;
  8 +use AukeySwrpc\Packer\PackerInterface;
  9 +use AukeySwrpc\Packer\SerializeLengthPacker;
  10 +use AukeySwrpc\Register\RegisterInterface;
  11 +use AukeySwrpc\Register\Service;
  12 +use AukeySwrpc\Request\Request;
  13 +
  14 +/**
  15 + * Class Client
  16 + *
  17 + * @package AukeySwrpc
  18 + * @author pengjch 202439 11:36:25
  19 + */
  20 +class Client
  21 +{
  22 + protected $services = [];
  23 + protected $connects = [];
  24 +
  25 +
  26 + const STRATEGY_RANDOM = 1;
  27 + const STRATEGY_WEIGHT = 2;
  28 +
  29 + protected $mode;
  30 + protected $timeout = 3;
  31 + protected array $options;
  32 + protected string $module;
  33 + protected int $strategy;
  34 + protected ?RegisterInterface $register = null;
  35 + protected ?PackerInterface $packer = null;
  36 +
  37 + protected array $defaultOptions
  38 + = [
  39 + 'open_length_check' => true,
  40 + 'package_length_type' => 'N',
  41 + 'package_length_offset' => 0, //第N个字节是包长度的值
  42 + 'package_body_offset' => 4, //第几个字节开始计算长度
  43 + 'package_max_length' => 81920, //协议最大长度
  44 + ];
  45 +
  46 + /**
  47 + * Client constructor.
  48 + *
  49 + * @param string $module
  50 + * @param array $services
  51 + * @param int $mode
  52 + * @param int $timeout
  53 + * @param array $options
  54 + */
  55 + public function __construct(string $module, array $services, $mode = SWOOLE_SOCK_TCP, $timeout = 3, $options = [])
  56 + {
  57 + $this->module = $module;
  58 + $this->services = $services;
  59 + $this->mode = $mode;
  60 + $this->timeout = $timeout;
  61 + if (empty($options)) {
  62 + $options = $this->defaultOptions;
  63 + }
  64 + $this->options = $options;
  65 +
  66 + }
  67 +
  68 + /**
  69 + * @param string $module
  70 + * @param string $host
  71 + * @param int $port
  72 + * @param int $mode
  73 + * @param array $options
  74 + * @return Client
  75 + * @author pengjch 2024313 18:31:17
  76 + */
  77 + public static function create(
  78 + string $module,
  79 + string $host,
  80 + int $port,
  81 + $mode = SWOOLE_SOCK_TCP,
  82 + $timeout = 3,
  83 + $options = []
  84 + ): Client {
  85 + $service = Service::build($host, $port, 1);
  86 + return new static($module, [$service], $mode, $timeout, $options);
  87 + }
  88 +
  89 + /**
  90 + * @param string $module
  91 + * @param RegisterInterface $register
  92 + * @param int $strategy
  93 + * @param int $mode
  94 + * @param int $timeout
  95 + * @param array $options
  96 + * @return Client
  97 + * @author pengjch 2024313 18:31:22
  98 + */
  99 + public static function createBalancer(
  100 + string $module,
  101 + RegisterInterface $register,
  102 + $strategy = self::STRATEGY_RANDOM,
  103 + $mode = SWOOLE_SOCK_TCP,
  104 + $timeout = 3,
  105 + $options = []
  106 + ): Client {
  107 + $client = new static($module, [], $mode, $timeout, $options);
  108 + $client->strategy = $strategy;
  109 + $client->addRegister($register);
  110 + return $client;
  111 + }
  112 +
  113 + /**
  114 + * @param RegisterInterface $register
  115 + * @return $this
  116 + * @author pengjch 2024313 18:27:20
  117 + */
  118 + public function addRegister(RegisterInterface $register): Client
  119 + {
  120 + $this->register = $register;
  121 + $this->services = $this->register->getServices($this->module);
  122 + return $this;
  123 + }
  124 +
  125 + /**
  126 + * @param PackerInterface $packer
  127 + * @return $this
  128 + * @author pengjch 2024313 18:27:24
  129 + */
  130 + public function addPacker(PackerInterface $packer): Client
  131 + {
  132 + $this->packer = $packer;
  133 + return $this;
  134 + }
  135 +
  136 + /**
  137 + * @return SwClient
  138 + * @throws RpcException
  139 + * @author pengjch 2024313 18:23:37
  140 + */
  141 + public function connect(): SwClient
  142 + {
  143 + $n = count($this->services);
  144 + if ($n == 0) {
  145 + throw new RpcException('No services available');
  146 + }
  147 +
  148 + /** @var Service $service */
  149 + if ($n == 1) { //单个服务节点
  150 + $service = $this->services[0];
  151 + $key = $service->getHost() . '_' . $service->getPort();
  152 + } else { //多个服务节点
  153 + $key = $this->getConnectKey();
  154 + }
  155 +
  156 + if (isset($this->connects[$key]) && $this->connects[$key]->isConnected()) {
  157 + return $this->connects[$key];
  158 + }
  159 + $client = new SwClient($this->mode ?: SWOOLE_SOCK_TCP);
  160 + if (!$client->connect($service->getHost(), $service->getPort(), $this->timeout ?? 3)) {
  161 + throw new RpcException("connect failed. Error: {$client->errCode}");
  162 + }
  163 + $client->set($this->options);
  164 + $this->connects[$key] = $client;
  165 + return $this->connects[$key];
  166 + }
  167 +
  168 + /**
  169 + * 发送请求
  170 + *
  171 + * @param Request $request
  172 + * @return mixed
  173 + * @throws RpcException
  174 + * @author pengjch 202439 13:35:25
  175 + */
  176 + public function send(Request $request)
  177 + {
  178 + /** @var \Swoole\Client $conn */
  179 + $conn = $this->connect();
  180 +
  181 + if (!$this->packer) {
  182 + $this->packer = new SerializeLengthPacker([
  183 + 'package_length_type' => $options['package_length_type'] ?? 'N',
  184 + 'package_body_offset' => $options['package_body_offset'] ?? 4,
  185 + ]);
  186 + }
  187 +
  188 + $request->setModule($this->module);
  189 + $conn->send($this->packer->pack($request));
  190 +
  191 + /** @var Response $response */
  192 + $response = @unserialize($conn->recv());
  193 + if (!($response instanceof Response)) {
  194 + throw new RpcException('The server return type is not a AukeySwrpc\Response');
  195 + }
  196 + if ($response->code == Response::RES_ERROR) {
  197 + throw new RpcException($response->msg);
  198 + }
  199 +
  200 + return $response->data['result'] ?? null;
  201 + }
  202 +
  203 + /**
  204 + * @return string
  205 + * @author pengjch 2024313 18:20:38
  206 + */
  207 + public function getConnectKey(): string
  208 + {
  209 + /** @var Service $service */
  210 + if ($this->strategy == self::STRATEGY_RANDOM) {
  211 + $service = array_rand($this->services);
  212 + return $service->getHost() . '_' . $service->getPort();
  213 + } else {
  214 + /** @var Service $service */
  215 + foreach ($this->services as $service) {
  216 + $totalWeight += $service->getWeight();
  217 + $sort[] = $service->getWeight();
  218 + $serviceArr[] = $service->toArray();
  219 + }
  220 +
  221 + array_multisort($serviceArr, SORT_DESC, $sort);
  222 +
  223 + $start = 0;
  224 + $rand = rand(1, $totalWeight);
  225 + foreach ($serviceArr as $service) {
  226 + if ($start + $service['weight'] >= $rand) {
  227 + return $service['host'] . '_' . $service['port'];
  228 + }
  229 + $start = $start + $service['weight'];
  230 + }
  231 + }
  232 + }
  233 +
  234 + /**
  235 + * 关闭客户端连接
  236 + *
  237 + * @return mixed
  238 + * @author pengjch 2024310 9:16:46
  239 + */
  240 + public function close()
  241 + {
  242 + foreach ($this->connects as $connect) {
  243 + $connect->close(true);
  244 + }
  245 + }
  246 +
  247 + /**
  248 + * 刷新节点服务信息
  249 + * 客户端使用长连接的情况下,需要起一个定时器来定时更新节点服务信息
  250 + *
  251 + * @author pengjch 2024313 18:24:23
  252 + */
  253 + public function refreshServices()
  254 + {
  255 + if ($this->register) {
  256 + $this->services = $this->register->getServices($this->module);
  257 + $this->connects = [];
  258 + }
  259 + }
  260 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc;
  4 +
  5 +trait Event
  6 +{
  7 + public function OnWorkerStart(\Swoole\Server $server, int $workerId)
  8 + {
  9 + }
  10 +
  11 + public function onStart(\Swoole\Server $server)
  12 + {
  13 + }
  14 +
  15 + public function onShutdown(\Swoole\Server $server)
  16 + {
  17 +
  18 + }
  19 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Exceptions;
  4 +
  5 +
  6 +use Exception;
  7 +
  8 +/**
  9 + * Class RequestException
  10 + *
  11 + * @package AukeySwrpc\Exceptions
  12 + * @author pengjch 202439 11:38:5
  13 + */
  14 +class RequestException extends Exception
  15 +{
  16 +
  17 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Exceptions;
  4 +
  5 +
  6 +use Exception;
  7 +
  8 +/**
  9 + * Class RpcException
  10 + *
  11 + * @package AukeySwrpc\Exceptions
  12 + * @author pengjch 202439 11:38:5
  13 + */
  14 +class RpcException extends Exception
  15 +{
  16 +
  17 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Exceptions;
  4 +
  5 +
  6 +use Exception;
  7 +
  8 +/**
  9 + * Class TypeException
  10 + *
  11 + * @package AukeySwrpc\Exceptions
  12 + * @author pengjch 202439 11:38:5
  13 + */
  14 +class TypeException extends Exception
  15 +{
  16 +
  17 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc;
  4 +
  5 +
  6 +use AukeySwrpc\Tracer\TracerContext;
  7 +use Zipkin\Endpoint;
  8 +use Zipkin\Reporters\Http;
  9 +use Zipkin\Samplers\BinarySampler;
  10 +use Zipkin\TracingBuilder;
  11 +
  12 +/**
  13 + * Class LogicService
  14 + *
  15 + * @package AukeySwrpc
  16 + * @author pengjch 2024311 11:10:18
  17 + */
  18 +class LogicService
  19 +{
  20 + protected $params;
  21 + protected $module;
  22 + protected $tracerUrl;
  23 + protected $tracerContext;
  24 + protected $clients = [];
  25 +
  26 + /**
  27 + * @return static
  28 + * @author pengjch 2024311 11:4:5
  29 + */
  30 + public static function factory()
  31 + {
  32 + return new static();
  33 + }
  34 +
  35 + /**
  36 + * 初始化链路追踪器
  37 + *
  38 + * @param $func
  39 + * @author pengjch 2024311 11:3:36
  40 + */
  41 + public function initTracer($func)
  42 + {
  43 + $reporterUrl = $this->tracerUrl ?: 'http://127.0.0.1:9411/api/v2/spans';
  44 + $endpoint = Endpoint::create($this->module);
  45 + $reporter = new Http(['endpoint_url' => $reporterUrl]);
  46 + $sampler = BinarySampler::createAsAlwaysSample();
  47 + $tracing = TracingBuilder::create()
  48 + ->havingLocalEndpoint($endpoint)
  49 + ->havingSampler($sampler)
  50 + ->havingReporter($reporter)
  51 + ->build();
  52 + $tracer = $tracing->getTracer();
  53 + $span = $tracer->newTrace();
  54 + $span->setName($func);
  55 + $span->start();
  56 + $span->finish();
  57 + $tracer->flush();
  58 +
  59 + $ctx = $span->getContext();
  60 + if ($this->tracerContext) {
  61 + $this->tracerContext->setTraceID($ctx->getTraceId());
  62 + $this->tracerContext->setParentID($ctx->getSpanId());
  63 + $this->tracerContext->setReporterUrl($reporterUrl);
  64 + } else {
  65 + $this->tracerContext = TracerContext::create($ctx->getTraceId(), $ctx->getSpanId(), $reporterUrl);
  66 + }
  67 + }
  68 +
  69 + /**
  70 + * @param $context
  71 + * @return $this
  72 + * @author pengjch 2024311 11:18:43
  73 + */
  74 + public function setTracerContext($context)
  75 + {
  76 + $this->tracerContext = $context;
  77 + return $this;
  78 + }
  79 +
  80 + /**
  81 + * @param $func
  82 + * @return null
  83 + * @author pengjch 2024311 11:4:1
  84 + */
  85 + public function getTracerContext($func)
  86 + {
  87 + if (empty($this->tracerUrl)) {
  88 + return null;
  89 + }
  90 + if (empty($this->tracerContext)) {
  91 + $this->initTracer($func);
  92 + }
  93 + return $this->tracerContext;
  94 + }
  95 +
  96 + /**
  97 + * @param array $params
  98 + * @return $this
  99 + * @author pengjch 2024311 11:15:47
  100 + */
  101 + public function setParams(array $params)
  102 + {
  103 + $this->params = $params;
  104 + return $this;
  105 + }
  106 +
  107 + /**
  108 + * @param string $url
  109 + * @return static $this
  110 + * @author pengjch 2024311 11:15:35
  111 + */
  112 + public function setTracerUrl(string $url)
  113 + {
  114 + $this->tracerUrl = $url;
  115 + return $this;
  116 + }
  117 +
  118 + /**
  119 + * @param string $name
  120 + * @return $this
  121 + * @author pengjch 2024311 11:59:4
  122 + */
  123 + public function setModule(string $name)
  124 + {
  125 + $this->module = $name;
  126 + return $this;
  127 + }
  128 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Middlewares;
  4 +
  5 +
  6 +use Closure;
  7 +use AukeySwrpc\Request\Request;
  8 +use AukeySwrpc\Response;
  9 +
  10 +/**
  11 + * Interface MiddlewareInterface
  12 + *
  13 + * @package AukeySwrpc\Middlewares
  14 + * @author pengjch 202439 11:37:39
  15 + */
  16 +interface MiddlewareInterface
  17 +{
  18 + function handle(Request $request, Closure $next): Response;
  19 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Middlewares;
  4 +
  5 +
  6 +use Closure;
  7 +use AukeySwrpc\Request\Request;
  8 +use AukeySwrpc\Response;
  9 +use Zipkin\Endpoint;
  10 +use Zipkin\Propagation\TraceContext;
  11 +use Zipkin\Reporters\Http;
  12 +use Zipkin\Samplers\BinarySampler;
  13 +use Zipkin\TracingBuilder;
  14 +
  15 +/**
  16 + * 链路追踪中间件
  17 + * Class TraceMiddleware
  18 + *
  19 + * @package AukeySwrpc\Middlewares
  20 + * @author pengjch 2024310 16:41:6
  21 + */
  22 +class TraceMiddleware implements MiddlewareInterface
  23 +{
  24 + function handle(Request $request, Closure $next): Response
  25 + {
  26 + $context = $request->getTraceContext();
  27 + if (!$context) {
  28 + return $next($request);
  29 + }
  30 +
  31 + $traceContext = TraceContext::create($context->getTraceID(), $context->getParentID(), null, true);
  32 + $endpoint = Endpoint::create($request->getModule());
  33 + $reporter = new Http(['endpoint_url' => $context->getReporterUrl()]);
  34 + $sampler = BinarySampler::createAsAlwaysSample();
  35 + $tracing = TracingBuilder::create()
  36 + ->havingLocalEndpoint($endpoint)
  37 + ->havingSampler($sampler)
  38 + ->havingReporter($reporter)
  39 + ->build();
  40 +
  41 + $tracer = $tracing->getTracer();
  42 + $span = $tracer->newChild($traceContext);
  43 + $span->setName($request->getMethod());
  44 + $span->start();
  45 + $span->tag('请求参数', serialize($request->getParams()));
  46 + $request->setTraceContext($span->getContext()->getTraceId(), $span->getContext()
  47 + ->getSpanId(), $context->getReporterUrl());
  48 +
  49 + $start = microtime(true);
  50 + $result = $next($request);
  51 + $end = microtime(true);
  52 +
  53 + $span->tag('响应状态码code', $result->code);
  54 + $span->tag('响应提示语msg', $result->msg);
  55 + $span->tag('响应耗时', $end - $start);
  56 + $span->finish();
  57 + $tracer->flush();
  58 +
  59 + return $result;
  60 + }
  61 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Packer;
  4 +
  5 +
  6 +use AukeySwrpc\Request\Request;
  7 +
  8 +/**
  9 + * Interface PackerInterface
  10 + *
  11 + * @package AukeySwrpc\Packer
  12 + * @author pengjch 202439 11:37:10
  13 + */
  14 +interface PackerInterface
  15 +{
  16 + function pack(Request $data):string;
  17 + function unpack(string $data);
  18 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Packer;
  4 +
  5 +
  6 +use AukeySwrpc\Request\Request;
  7 +
  8 +/**
  9 + * Class SerializeEofPacker
  10 + *
  11 + * @package AukeySwrpc\Packer
  12 + * @author pengjch 202439 11:37:17
  13 + */
  14 +class SerializeEofPacker implements PackerInterface
  15 +{
  16 + /**
  17 + * @var string
  18 + */
  19 + protected $eof;
  20 +
  21 + public function __construct(array $options = [])
  22 + {
  23 + $this->eof = $options['settings']['package_eof'] ?? "\r\n";
  24 + }
  25 +
  26 + public function pack(Request $data): string
  27 + {
  28 + return serialize($data);
  29 + }
  30 +
  31 + public function unpack(string $data)
  32 + {
  33 + return unserialize($data);
  34 + }
  35 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Packer;
  4 +
  5 +
  6 +use AukeySwrpc\Request\Request;
  7 +
  8 +/**
  9 + * Class SerializeLengthPacker
  10 + *
  11 + * @package AukeySwrpc\Packer
  12 + * @author pengjch 202439 11:37:27
  13 + */
  14 +class SerializeLengthPacker implements PackerInterface
  15 +{
  16 + /**
  17 + * @var string
  18 + */
  19 + protected $type;
  20 +
  21 + /**
  22 + * @var int
  23 + */
  24 + protected $length;
  25 +
  26 + protected $defaultOptions
  27 + = [
  28 + 'package_length_type' => 'N',
  29 + 'package_body_offset' => 4,
  30 + ];
  31 +
  32 + public function __construct(array $options = [])
  33 + {
  34 + $options = array_merge($this->defaultOptions, $options['settings'] ?? []);
  35 +
  36 + $this->type = $options['package_length_type'];
  37 + $this->length = $options['package_body_offset'];
  38 + }
  39 +
  40 + public function pack(Request $data): string
  41 + {
  42 + $data = serialize($data);
  43 + return pack($this->type, strlen($data)) . $data;
  44 + }
  45 +
  46 + public function unpack(string $data)
  47 + {
  48 + $package = unpack('N', $data);
  49 + $len = $package[1];
  50 +
  51 + //合并unserialize和substr,以减少内存拷贝 https://wenda.swoole.com/detail/107587
  52 + if (function_exists('swoole_substr_unserialize')) {
  53 + return swoole_substr_unserialize($data, $this->length, $len);
  54 + }
  55 +
  56 + $data = substr($data, $this->length, $len);
  57 + return unserialize($data);
  58 + }
  59 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Register;
  4 +
  5 +
  6 +use SensioLabs\Consul\ServiceFactory;
  7 +use SensioLabs\Consul\Services\Agent;
  8 +use SensioLabs\Consul\Services\AgentInterface as AgentInterfaceAlias;
  9 +use SensioLabs\Consul\Services\Catalog;
  10 +use SensioLabs\Consul\Services\CatalogInterface;
  11 +use SensioLabs\Consul\Services\Health;
  12 +use AukeySwrpc\Exceptions\RpcException;
  13 +
  14 +class Consul implements RegisterInterface
  15 +{
  16 + protected $sf;
  17 + protected array $options;
  18 + protected array $serviceCache
  19 + = [
  20 + 'ttl' => 10,
  21 + 'services' => [],
  22 + 'lastUpdateTime' => 0,
  23 + ];
  24 +
  25 + public function __construct($uri = 'http://127.0.0.1:8500', $options = [])
  26 + {
  27 + $this->options = $options;
  28 + $this->sf = new ServiceFactory([
  29 + 'base_uri' => $uri
  30 + ]);
  31 + }
  32 +
  33 + public function getName(): string
  34 + {
  35 + return 'Consul';
  36 + }
  37 +
  38 + /**
  39 + * 注册节点
  40 + *
  41 + * @param string $module
  42 + * @param string $host
  43 + * @param $port
  44 + * @param int $weight
  45 + * @author pengjch 202439 23:17:5
  46 + */
  47 + public function register($module, $host, $port, $weight = 1)
  48 + {
  49 + $id = $host . '_' . $port;
  50 + /** @var Agent $agent */
  51 + $agent = $this->sf->get(AgentInterfaceAlias::class);
  52 + $agent->registerService([
  53 + 'ID' => $id,
  54 + 'Name' => $module,
  55 + 'Port' => $port,
  56 + 'Address' => $host,
  57 + 'Tags' => [
  58 + 'port_' . $port,
  59 + ],
  60 + 'Weights' => [
  61 + 'Passing' => $weight,
  62 + 'Warning' => 1,
  63 + ],
  64 + 'Check' => [
  65 + 'TCP' => $host . ':' . $port,
  66 + 'Interval' => $this->options['interval'] ?? '10s',
  67 + 'Timeout' => $this->options['timeout'] ?? '5s',
  68 + 'DeregisterCriticalServiceAfter' => $this->options['deregisterCriticalServiceAfter'] ?? '30s',
  69 + ],
  70 + ]);
  71 + }
  72 +
  73 + /**
  74 + * 注销节点
  75 + * http://127.0.0.1:8500/v1/agent/service/deregister/service_id
  76 + *
  77 + * @param $host
  78 + * @param $port
  79 + * @author pengjch 202439 23:16:51
  80 + */
  81 + public function unRegister($host, $port)
  82 + {
  83 + $id = $host . '_' . $port;
  84 + /** @var Agent $agent */
  85 + $agent = $this->sf->get(AgentInterfaceAlias::class);
  86 + $agent->deregisterService($id);
  87 + }
  88 +
  89 + /**
  90 + * 获取模块下所有的服务
  91 + *
  92 + * @param string $module
  93 + * @return array
  94 + * @author pengjch 2024310 9:44:16
  95 + */
  96 + public function getServices(string $module): array
  97 + {
  98 + $cache = $this->serviceCache;
  99 + $ttl = $this->options['ttl'] ?? $cache['ttl'];
  100 +
  101 + //本地缓存所有节点信息,避免每次请求都要从consul拉一遍数据
  102 + if ($cache['lastUpdateTime'] + $ttl < time()) {
  103 + $health = new Health();
  104 + $servers = $health->service($module)->json();
  105 + if (empty($servers)) {
  106 + return [];
  107 + }
  108 + $result = [];
  109 + foreach ($servers as $server) {
  110 + $result[] = Service::build($server['Service']['Address'], $server['Service']['Port'], $server['Service']['Weights']['Passing']);
  111 + }
  112 + $cache['service'] = $result;
  113 + $cache['lastUpdateTime'] = time();
  114 + }
  115 +
  116 + return $cache['service'];
  117 + }
  118 +
  119 + /**
  120 + * 随机获取一个服务
  121 + *
  122 + * @param string $module
  123 + * @return Service
  124 + * @author pengjch 2024310 9:44:27
  125 + */
  126 + public function getRandomService(string $module): Service
  127 + {
  128 + $services = $this->getServices($module);
  129 + if (!$services) {
  130 + throw new RpcException('It has not register module');
  131 + }
  132 +
  133 + return $services[rand(0, count($services) - 1)];
  134 + }
  135 +
  136 + /**
  137 + * 获取权重服务
  138 + *
  139 + * @param string $module
  140 + * @return Service
  141 + * @author pengjch 2024310 9:44:38
  142 + */
  143 + public function getWeightService(string $module): Service
  144 + {
  145 + $serviceArr = [];
  146 + $totalWeight = 0;
  147 + $services = $this->getServices($module);
  148 + if (!$services) {
  149 + throw new RpcException('It has not register module');
  150 + }
  151 +
  152 + /** @var Service $service */
  153 + foreach ($services as $service) {
  154 + $totalWeight += $service->getWeight();
  155 + $sort[] = $service->getWeight();
  156 + $serviceArr[] = $service->toArray();
  157 + }
  158 +
  159 + array_multisort($serviceArr, SORT_DESC, $sort);
  160 +
  161 + $start = 0;
  162 + $rand = rand(1, $totalWeight);
  163 + foreach ($serviceArr as $service) {
  164 + if ($start + $service['weight'] >= $rand) {
  165 + return Service::build($service['host'], $service['port'], $service['weight']);
  166 + }
  167 + $start = $start + $service['weight'];
  168 + }
  169 + }
  170 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Register;
  4 +
  5 +
  6 +/**
  7 + * Interface RegisterInterface
  8 + *
  9 + * @package AukeySwrpc\Register
  10 + * @author pengjch 202439 16:23:35
  11 + */
  12 +interface RegisterInterface
  13 +{
  14 + function getName(): string;
  15 +
  16 + function register($module, $host, $port, $weight = 1);
  17 +
  18 + function unRegister($host, $port);
  19 +
  20 + function getServices(string $module): array;
  21 +
  22 + function getRandomService(string $module): Service;
  23 +
  24 + function getWeightService(string $module): Service;
  25 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Register;
  4 +
  5 +
  6 +/**
  7 + * 注册中心服务
  8 + * Class Service
  9 + *
  10 + * @package AukeySwrpc\Register
  11 + * @author pengjch 2024311 10:25:46
  12 + */
  13 +class Service
  14 +{
  15 + protected $host;
  16 + protected $port;
  17 + protected $weight;
  18 +
  19 + public function __construct($host, $port, $weight)
  20 + {
  21 + $this->host = $host;
  22 + $this->port = $port;
  23 + $this->weight = $weight;
  24 + }
  25 +
  26 + public static function build($host, $port, $weight)
  27 + {
  28 + return new static($host, $port, $weight);
  29 + }
  30 +
  31 + public function getHost()
  32 + {
  33 + return $this->host;
  34 + }
  35 +
  36 + public function getPort()
  37 + {
  38 + return $this->port;
  39 + }
  40 +
  41 + public function getWeight()
  42 + {
  43 + return $this->weight;
  44 + }
  45 +
  46 + public function toArray(): array
  47 + {
  48 + return [
  49 + 'host' => $this->host,
  50 + 'port' => $this->port,
  51 + 'weight' => $this->weight
  52 + ];
  53 + }
  54 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Request;
  4 +
  5 +
  6 +/**
  7 + * Class AsyncRequest
  8 + *
  9 + * @package AukeySwrpc\Request
  10 + * @author pengjch 2024313 9:10:2
  11 + */
  12 +class AsyncRequest extends Request
  13 +{
  14 + public function init()
  15 + {
  16 + $this->setSync(false);
  17 + $this->setSystem(false);
  18 + }
  19 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Request;
  4 +
  5 +
  6 +use AukeySwrpc\Tracer\TracerContext;
  7 +
  8 +abstract class Request
  9 +{
  10 + protected string $module;
  11 + protected string $method;
  12 + protected array $params;
  13 + protected bool $isSync = true; //是否同步请求,默认是
  14 + protected bool $isSystem = false; //是否系统请求,默认否
  15 + protected $error;
  16 + protected ?TracerContext $traceContext;
  17 +
  18 + public static function create($method, $params, ?TracerContext $traceContext = null)
  19 + {
  20 + return new static ($method, $params, $traceContext);
  21 + }
  22 +
  23 + public function __construct($method, $params, ?TracerContext $traceContext = null)
  24 + {
  25 + $this->method = $method;
  26 + $this->params = $params;
  27 + $this->traceContext = $traceContext;
  28 + $this->init();
  29 + }
  30 +
  31 + abstract public function init();
  32 +
  33 + public function getModule(): string
  34 + {
  35 + return $this->module;
  36 + }
  37 +
  38 + public function getMethod(): string
  39 + {
  40 + return $this->method;
  41 + }
  42 +
  43 + public function getParams(): array
  44 + {
  45 + return $this->params;
  46 + }
  47 +
  48 + public function setParams(array $params)
  49 + {
  50 + $this->params = $params;
  51 + }
  52 +
  53 + public function setModule(string $name)
  54 + {
  55 + $this->module = $name;
  56 + }
  57 +
  58 + public function mergeParams(array $params)
  59 + {
  60 + $this->params = array_merge($this->params, $params);
  61 + }
  62 +
  63 + public function getTraceContext(): ?TracerContext
  64 + {
  65 + return $this->traceContext;
  66 + }
  67 +
  68 + public function setTraceContext($traceID, $parentID, $url)
  69 + {
  70 + $this->traceContext = TracerContext::create($traceID, $parentID, $url);
  71 + }
  72 +
  73 + public function setSync(bool $value)
  74 + {
  75 + $this->isSync = $value;
  76 + }
  77 +
  78 + public function isSync(): bool
  79 + {
  80 + return $this->isSync;
  81 + }
  82 +
  83 + public function setSystem(bool $value)
  84 + {
  85 + $this->isSystem = $value;
  86 + }
  87 +
  88 + public function isSystem(): bool
  89 + {
  90 + return $this->isSystem;
  91 + }
  92 +
  93 + public function getError()
  94 + {
  95 + return $this->error;
  96 + }
  97 +
  98 + public function setError($err)
  99 + {
  100 + $this->error = $err;
  101 + }
  102 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Request;
  4 +
  5 +/**
  6 + * Class SyncRequest
  7 + *
  8 + * @package AukeySwrpc\Request
  9 + * @author pengjch 2024313 9:9:54
  10 + */
  11 +class SyncRequest extends Request
  12 +{
  13 + public function init()
  14 + {
  15 + $this->setSync(true);
  16 + $this->setSystem(false);
  17 + }
  18 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Request;
  4 +
  5 +
  6 +/**
  7 + * Class AsyncRequest
  8 + *
  9 + * @package AukeySwrpc\Request
  10 + * @author pengjch 2024313 9:10:2
  11 + */
  12 +class SystemRequest extends Request
  13 +{
  14 + public function init()
  15 + {
  16 + $this->setSync(true);
  17 + $this->setSystem(true);
  18 + }
  19 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc;
  4 +
  5 +
  6 +/**
  7 + * Class Response
  8 + *
  9 + * @package AukeySwrpc
  10 + * @author pengjch 202439 11:36:9
  11 + */
  12 +class Response
  13 +{
  14 + const RES_ERROR = 0;
  15 + const RES_SUCCESS = 1;
  16 +
  17 + public string $msg;
  18 + public int $code;
  19 + public array $data;
  20 +
  21 + public function __construct($code, $msg, $data)
  22 + {
  23 + $this->data = $data;
  24 + $this->code = $code;
  25 + $this->msg = $msg;
  26 + }
  27 +
  28 + public static function error($msg, $code = self::RES_ERROR, $data = []): Response
  29 + {
  30 + return new static($code, $msg, $data);
  31 + }
  32 +
  33 + public static function success($data = [], $msg = 'success', $code = self::RES_SUCCESS): Response
  34 + {
  35 + return new static($code, $msg, $data);
  36 + }
  37 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc;
  4 +
  5 +
  6 +use Monolog\Handler\StreamHandler;
  7 +use Monolog\Logger;
  8 +use Psr\Log\LoggerInterface;
  9 +use AukeySwrpc\Middlewares\TraceMiddleware;
  10 +use AukeySwrpc\Packer\SerializeLengthPacker;
  11 +use AukeySwrpc\Register\RegisterInterface;
  12 +use AukeySwrpc\Middlewares\MiddlewareInterface;
  13 +use AukeySwrpc\Packer\PackerInterface;
  14 +use AukeySwrpc\Request\Request;
  15 +
  16 +/**
  17 + * Class Server
  18 + *
  19 + * @package AukeySwrpc
  20 + * @author pengjch 202439 11:35:52
  21 + */
  22 +class Server
  23 +{
  24 + use Event;
  25 +
  26 + protected string $module;
  27 + protected string $host;
  28 + protected int $port;
  29 + protected int $weight = 1;
  30 + protected array $options;
  31 + protected array $defaultOptions
  32 + = [
  33 + 'open_length_check' => true,
  34 + 'package_length_type' => 'N',
  35 + 'package_length_offset' => 0, //第N个字节是包长度的值
  36 + 'package_body_offset' => 4, //第几个字节开始计算长度
  37 + 'package_max_length' => 81920, //协议最大长度
  38 + ];
  39 +
  40 + /** @var PackerInterface $packer */
  41 + protected $packer;
  42 +
  43 + /** @var Service $service */
  44 + protected $service;
  45 +
  46 + /** @var LoggerInterface $logger */
  47 + protected $logger;
  48 +
  49 + /** @var RegisterInterface $register */
  50 + protected $register;
  51 +
  52 + /** @var \Swoole\Server $server */
  53 + protected \Swoole\Server $server;
  54 +
  55 + private array $middlewares;
  56 +
  57 + public function __construct(
  58 + string $module,
  59 + string $host,
  60 + int $port,
  61 + array $options = [],
  62 + $mode = SWOOLE_PROCESS,
  63 + $socketType = SWOOLE_SOCK_TCP,
  64 + LoggerInterface $logger = null
  65 + ) {
  66 + $this->module = $module;
  67 + $this->host = $host;
  68 + $this->port = $port;
  69 +
  70 + $this->setDefaultOptions($options);
  71 + $this->setDefaultLogger($logger);
  72 + $this->setCoreMiddleware();
  73 +
  74 + $this->service = new Service($this->logger);
  75 +
  76 + $server = new \Swoole\Server($host, $port, $mode ?: SWOOLE_PROCESS, $socketType ?: SWOOLE_SOCK_TCP);
  77 + $server->set($this->options);
  78 + $server->on('Start', [$this, 'onStart']);
  79 + $server->on('Shutdown', [$this, 'onShutdown']);
  80 + $server->on('WorkerStart', [$this, 'onWorkerStart']);
  81 + $server->on('Connect', [$this, 'OnConnect']);
  82 + $server->on('Receive', [$this, 'OnReceive']);
  83 + $server->on('Close', [$this, 'OnClose']);
  84 + $server->on('Task', [$this, 'OnTask']);
  85 + $server->on('Finish', [$this, 'OnFinish']);
  86 + $this->server = $server;
  87 + }
  88 +
  89 + /**
  90 + * 设置节点权重
  91 + *
  92 + * @param int $weight
  93 + * @return Server
  94 + * @author pengjch 2024313 10:55:39
  95 + */
  96 + public function weight(int $weight): Server
  97 + {
  98 + $this->weight = $weight;
  99 + return $this;
  100 + }
  101 +
  102 + /**
  103 + * 设置默认选项
  104 + *
  105 + * @param $options
  106 + * @author pengjch 2024311 10:35:3
  107 + */
  108 + protected function setDefaultOptions($options)
  109 + {
  110 + if (empty($options)) {
  111 + $options = $this->defaultOptions;
  112 + }
  113 +
  114 + $this->options = $options;
  115 +
  116 + //请求数量超过10000重启
  117 + if (empty($this->options['max_request'])) {
  118 + $this->options['max_request'] = 10000;
  119 + }
  120 + //默认task数量
  121 + if (empty($this->options['task_worker_num'])) {
  122 + $this->options['task_worker_num'] = swoole_cpu_num() * 2;
  123 + }
  124 + //task请求数超过10000则重启
  125 + if (empty($this->options['task_max_request'])) {
  126 + $this->options['task_max_request'] = 10000;
  127 + }
  128 + //10s没有数据传输就进行检测
  129 + if (empty($this->options['tcp_keepidle'])) {
  130 + $this->options['tcp_keepidle'] = 10;
  131 + }
  132 + //3s探测一次
  133 + if (empty($this->options['tcp_keepinterval'])) {
  134 + $this->options['tcp_keepinterval'] = 3;
  135 + }
  136 + //探测的次数,超过5次后还没回包close此连接
  137 + if (empty($this->options['tcp_keepcount'])) {
  138 + $this->options['tcp_keepcount'] = 5;
  139 + }
  140 + }
  141 +
  142 + /**
  143 + * 设置默认日志处理器
  144 + *
  145 + * @param LoggerInterface|null $logger
  146 + * @author pengjch 2024311 10:34:19
  147 + */
  148 + protected function setDefaultLogger(LoggerInterface $logger = null)
  149 + {
  150 + if (empty($logger)) {
  151 + $logger = new Logger('AukeySwrpc');
  152 + $logger->pushHandler(new StreamHandler(STDOUT, Logger::DEBUG));
  153 + }
  154 + $this->logger = $logger;
  155 + }
  156 +
  157 + /**
  158 + * 设置核心中间件
  159 + *
  160 + * @author pengjch 2024311 10:34:5
  161 + */
  162 + protected function setCoreMiddleware()
  163 + {
  164 + $this->middlewares[] = TraceMiddleware::class;
  165 + }
  166 +
  167 + /**
  168 + * 添加中间件,支持匿名函数和实现类
  169 + * addMiddleware
  170 + *
  171 + * @param mixed ...$middlewares
  172 + * @author pengjch 202439 11:35:11
  173 + */
  174 + public function addMiddleware(...$middlewares)
  175 + {
  176 + foreach ($middlewares as $middleware) {
  177 + if (is_string($middleware) && class_exists($middleware)) {
  178 + $middleware = new $middleware();
  179 + }
  180 + if (!($middleware instanceof \Closure) && !($middleware instanceof MiddlewareInterface)) {
  181 + $this->logger->warning('Skip illegal Middleware.');
  182 + continue;
  183 + }
  184 + $this->middlewares[] = $middleware;
  185 + }
  186 + }
  187 +
  188 + /**
  189 + * 添加服务
  190 + * addService
  191 + *
  192 + * @param $service
  193 + * @param string $prefix
  194 + * @return Server
  195 + * @author pengjch 202439 11:35:2
  196 + */
  197 + public function addService($service, $prefix = ''): Server
  198 + {
  199 + $this->service->addInstance($service, $prefix);
  200 + return $this;
  201 + }
  202 +
  203 + /**
  204 + * @param $key
  205 + * @return mixed|null
  206 + * @author pengjch 2024312 16:11:12
  207 + */
  208 + public function getService($key)
  209 + {
  210 + return $this->service->getService($key);
  211 + }
  212 +
  213 + /**
  214 + * 注册发现中心
  215 + *
  216 + * @param $register
  217 + * @return Server
  218 + * @author pengjch 202439 16:38:51
  219 + */
  220 + public function addRegister($register): Server
  221 + {
  222 + $this->register = $register;
  223 + return $this;
  224 + }
  225 +
  226 + /**
  227 + * 添加日志处理器
  228 + *
  229 + * @param $logger
  230 + * @author pengjch 202439 12:20:57
  231 + */
  232 + public function addLogger($logger)
  233 + {
  234 + $this->logger = $logger;
  235 + }
  236 +
  237 + /**
  238 + * 添加包解析器
  239 + *
  240 + * @param $packer
  241 + * @author pengjch 202439 12:45:53
  242 + */
  243 + public function addPacker($packer)
  244 + {
  245 + $this->packer = $packer;
  246 + }
  247 +
  248 + /**
  249 + * 注册服务到consul
  250 + * onWorkerStart 和 onStart 回调是在不同进程中并行执行的,不存在先后顺序
  251 + *
  252 + * @param \Swoole\Server $server
  253 + * @author pengjch 202439 23:11:10
  254 + */
  255 + public function onStart(\Swoole\Server $server)
  256 + {
  257 + if ($this->register) {
  258 + $this->logger->info(sprintf('Register server[%s:%d] to %s.', $this->host, $this->port, $this->register->getName()));
  259 + $this->register->register($this->module, $this->host, $this->port, $this->weight);
  260 + }
  261 + }
  262 +
  263 + /**
  264 + * 注销服务
  265 + * 强制 kill 进程不会回调 onShutdown
  266 + * 需要使用 kill -15 来发送 SIGTERM 信号到主进程才能按照正常的流程终止
  267 + *
  268 + * @param \Swoole\Server $server
  269 + * @author pengjch 202439 23:14:40
  270 + */
  271 + public function onShutdown(\Swoole\Server $server)
  272 + {
  273 + if ($this->register) {
  274 + $this->logger->info(sprintf('UnRegister server[%s:%d] from register.', $this->host, $this->port));
  275 + $this->register->unRegister($this->host, $this->port);
  276 + }
  277 + }
  278 +
  279 + /**
  280 + * server接收请求
  281 + *
  282 + * @param \Swoole\Server $server
  283 + * @param $fd
  284 + * @param $reactor_id
  285 + * @param $data
  286 + * @return mixed
  287 + * @author pengjch 202439 11:34:0
  288 + */
  289 + public function onReceive(\Swoole\Server $server, $fd, $reactor_id, $data)
  290 + {
  291 + /** @var Request $request */
  292 + $request = $this->packer->unpack($data);
  293 + //系统请求
  294 + if ($request->isSystem()) {
  295 + return $server->send($fd, serialize($this->doSystemRequest($request)));
  296 + }
  297 + //同步请求
  298 + if ($request->isSync()) {
  299 + return $server->send($fd, serialize($this->doRequest($request)));
  300 + }
  301 + //异步请求
  302 + $server->task($request);
  303 + return $server->send($fd, serialize(Response::success(['result' => 'success'])));
  304 + }
  305 +
  306 + /**
  307 + * 执行请求
  308 + *
  309 + * @param Request $request
  310 + * @return Response
  311 + * @author pengjch 2024313 9:37:20
  312 + */
  313 + public function doRequest(Request $request): Response
  314 + {
  315 + try {
  316 + $handler = $this->getRequestHandler();
  317 + } catch (\ReflectionException $e) {
  318 + return Response::error($e->getMessage());
  319 + }
  320 +
  321 + $response = $handler($request);
  322 + if (!($response instanceof Response)) {
  323 + $msg = 'The middleware must return the response type';
  324 + $this->logger->error($msg);
  325 + $response = Response::error($msg);
  326 + }
  327 +
  328 + return $response;
  329 + }
  330 +
  331 + /**
  332 + * 系统请求
  333 + *
  334 + * @param Request $request
  335 + * @return Response
  336 + * @author pengjch 2024323 10:46:55
  337 + */
  338 + public function doSystemRequest(Request $request): Response
  339 + {
  340 + if ($request->getMethod() == 'stats') {
  341 + return Response::success(['result' => $this->server->stats()]);
  342 + } else {
  343 + return Response::error($request->getMethod() . ' is not supported');
  344 + }
  345 + }
  346 +
  347 + /**
  348 + * @return mixed
  349 + * @throws \ReflectionException
  350 + * @author pengjch 2024312 16:36:52
  351 + */
  352 + public function getRequestHandler()
  353 + {
  354 + return array_reduce(array_reverse($this->middlewares), function ($stack, $next) {
  355 + return function ($request) use ($stack, $next) {
  356 + if ($next instanceof \Closure) {
  357 + return $next($request, $stack);
  358 + } elseif (is_string($next) && class_exists($next)) {
  359 + return (new $next())->handle($request, $stack);
  360 + } else {
  361 + return $next->handle($request, $stack);
  362 + }
  363 + };
  364 + }, function ($request) {
  365 + return $this->service->call($request);
  366 + });
  367 + }
  368 +
  369 + /**
  370 + * 异步处理请求
  371 + *
  372 + * @param $server
  373 + * @param $taskID
  374 + * @param $reactorID
  375 + * @param $data
  376 + * @return Response
  377 + * @author pengjch 2024313 9:40:37
  378 + */
  379 + public function OnTask($server, $taskID, $reactorID, $data): Response
  380 + {
  381 + $this->logger->debug('AsyncTask: Start', ['taskID' => $taskID]);
  382 + return $this->doRequest($data);
  383 + }
  384 +
  385 + /**
  386 + * 完成异步任务回调
  387 + *
  388 + * @param $server
  389 + * @param $taskID
  390 + * @param $data
  391 + * @author pengjch 2024313 9:49:44
  392 + */
  393 + public function OnFinish($server, $taskID, $data)
  394 + {
  395 + $this->logger->debug('AsyncTask: Finish', ['taskID' => $taskID, 'data' => $data]);
  396 + }
  397 +
  398 + /**
  399 + * OnClose
  400 + *
  401 + * @param $server
  402 + * @param $fd
  403 + * @author pengjch 202439 11:34:48
  404 + */
  405 + public function OnClose($server, $fd)
  406 + {
  407 + $this->logger->debug('Client: Close');
  408 + }
  409 +
  410 + /**
  411 + * OnConnect
  412 + *
  413 + * @param $server
  414 + * @param $fd
  415 + * @author pengjch 202439 11:34:52
  416 + */
  417 + public function OnConnect($server, $fd)
  418 + {
  419 + $this->logger->debug('Client: Connect.');
  420 + }
  421 +
  422 + /**
  423 + * start
  424 + *
  425 + * @author pengjch 202439 11:34:56
  426 + */
  427 + public function start(): bool
  428 + {
  429 + //可用服务数量
  430 + if ($this->service->count() == 0) {
  431 + $this->logger->error('There is no service available.');
  432 + return false;
  433 + }
  434 + //默认使用固定包头+包体方式解决粘包问题
  435 + if (empty($this->packer)) {
  436 + $this->packer = new SerializeLengthPacker([
  437 + 'package_length_type' => $this->options['package_length_type'] ?? 'N',
  438 + 'package_body_offset' => $this->options['package_body_offset'] ?? 4,
  439 + ]);
  440 + }
  441 +
  442 + $this->logger->info(sprintf('Rpc server[%s:%s] start.', $this->host, $this->port));
  443 + $this->server->start();
  444 + return true;
  445 + }
  446 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc;
  4 +
  5 +
  6 +use Psr\Log\LoggerInterface;
  7 +use ReflectionClass;
  8 +use ReflectionMethod;
  9 +use AukeySwrpc\Request\Request;
  10 +
  11 +/**
  12 + * Class Service
  13 + *
  14 + * @package AukeySwrpc
  15 + * @author pengjch 202439 11:39:41
  16 + */
  17 +class Service
  18 +{
  19 + private array $services = [];
  20 + protected array $filers
  21 + = [
  22 + 'factory',
  23 + 'initTracer',
  24 + 'setModule',
  25 + 'setTracerUrl',
  26 + 'setParams',
  27 + 'setTracerContext',
  28 + 'getTracerContext'
  29 + ];
  30 +
  31 + /** @var LoggerInterface $logger */
  32 + private $logger;
  33 +
  34 + public function __construct($logger)
  35 + {
  36 + $this->logger = $logger;
  37 + }
  38 +
  39 + /**
  40 + * 注册服务实例
  41 + *
  42 + * @param $obj
  43 + * @param $prefix
  44 + * @return bool
  45 + * @author pengjch 202438 13:43:21
  46 + */
  47 + public function addInstance($obj, $prefix = ''): bool
  48 + {
  49 + if (is_string($obj)) {
  50 + $obj = new $obj();
  51 + }
  52 + if (!is_object($obj)) {
  53 + $this->logger->error('Service is not an object.', ['service' => $obj]);
  54 + return false;
  55 + }
  56 + if (!($obj instanceof LogicService)) {
  57 + $this->logger->error('The Service does not inherit LogicService', ['service' => get_class($obj)]);
  58 + return false;
  59 + }
  60 + $className = get_class($obj);
  61 + $methods = get_class_methods($obj);
  62 + foreach ($methods as $method) {
  63 + if (in_array($method, $this->filers)) {
  64 + continue;
  65 + }
  66 + if (strlen($prefix) > 0) {
  67 + $key = $prefix . '_' . $className . '_' . $method;
  68 + } else {
  69 + $key = $className . '_' . $method;
  70 + }
  71 + $this->services[$key] = $className;
  72 + $this->logger->info(sprintf('import %s => %s.', $key, $className));
  73 + }
  74 +
  75 + return true;
  76 + }
  77 +
  78 + /**
  79 + * 获取服务
  80 + *
  81 + * @param $key
  82 + * @return mixed|null
  83 + * @author pengjch 202438 13:43:17
  84 + */
  85 + public function getService($key)
  86 + {
  87 + return $this->services[$key] ?? null;
  88 + }
  89 +
  90 + /**
  91 + * 获取所有服务
  92 + * getServices
  93 + *
  94 + * @return array
  95 + * @author pengjch 202438 15:23:58
  96 + */
  97 + public function getServices(): array
  98 + {
  99 + return $this->services;
  100 + }
  101 +
  102 + /**
  103 + * count
  104 + *
  105 + * @return int
  106 + * @author pengjch 202439 12:56:46
  107 + */
  108 + public function count(): int
  109 + {
  110 + return count($this->services);
  111 + }
  112 +
  113 + /**
  114 + * @param $key
  115 + * @return bool
  116 + * @author pengjch 202438 14:32:50
  117 + */
  118 + public function isExist($key): bool
  119 + {
  120 + return isset($this->services[$key]);
  121 + }
  122 +
  123 + /**
  124 + * 调用服务
  125 + *
  126 + * @param Request $request
  127 + * @return Response
  128 + * @throws \ReflectionException
  129 + * @author pengjch 202439 10:17:59
  130 + */
  131 + public function call(Request $request): Response
  132 + {
  133 + if ($err = $request->getError()) {
  134 + return Response::error($err);
  135 + }
  136 +
  137 + $service = $this->getService($request->getMethod());
  138 + if (!$service) {
  139 + $this->logger->debug('service is not exist.', ['method' => $request->getMethod()]);
  140 + return Response::error('service is not exist.');
  141 + }
  142 +
  143 + $methodArr = explode('_', $request->getMethod());
  144 + $methodName = array_pop($methodArr);
  145 + $reflect = new ReflectionClass($service);
  146 + $instance = $reflect->newInstanceArgs();
  147 + if (!method_exists($instance, $methodName)) {
  148 + $this->logger->debug('method is not exist.', ['method' => $request->getMethod()]);
  149 + return Response::error(sprintf('%s method[%s] is not exist.', $service, $methodName));
  150 + }
  151 +
  152 + $ctx = $request->getTraceContext();
  153 + if ($ctx && method_exists($instance, 'setTracerContext')) {
  154 + $instance->setTracerUrl($ctx->getReporterUrl())->setTracerContext($ctx);
  155 + }
  156 +
  157 + try {
  158 + $methodObj = new ReflectionMethod($reflect->getName(), $methodName);
  159 + $result = $methodObj->invokeArgs($instance, $request->getParams());
  160 + } catch (\Throwable $e) {
  161 + return Response::error($e->getMessage());
  162 + }
  163 +
  164 + return Response::success([
  165 + 'result' => $result
  166 + ]);
  167 + }
  168 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpc\Tracer;
  4 +
  5 +
  6 +/**
  7 + * 链路追踪上下文
  8 + * Class TracerContext
  9 + *
  10 + * @package AukeySwrpc\Tracer
  11 + * @author pengjch 2024311 10:21:34
  12 + */
  13 +class TracerContext
  14 +{
  15 + protected $traceID;
  16 + protected $parentID;
  17 + protected $reporterUrl;
  18 +
  19 + public function __construct($traceID, $parentID, $reporterUrl)
  20 + {
  21 + $this->traceID = $traceID;
  22 + $this->parentID = $parentID;
  23 + $this->reporterUrl = $reporterUrl;
  24 + }
  25 +
  26 + public static function create($traceID, $parentID, $reporterUrl)
  27 + {
  28 + return new static($traceID, $parentID, $reporterUrl);
  29 + }
  30 +
  31 + public function setTraceID($traceID)
  32 + {
  33 + $this->traceID = $traceID;
  34 + }
  35 +
  36 + public function setParentID($parentID)
  37 + {
  38 + $this->parentID = $parentID;
  39 + }
  40 +
  41 + public function setReporterUrl($url)
  42 + {
  43 + $this->reporterUrl = $url;
  44 + }
  45 +
  46 + public function getTraceID()
  47 + {
  48 + return $this->traceID;
  49 + }
  50 +
  51 + public function getParentID()
  52 + {
  53 + return $this->parentID;
  54 + }
  55 +
  56 + public function getReporterUrl()
  57 + {
  58 + return $this->reporterUrl;
  59 + }
  60 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpcTests;
  4 +
  5 +
  6 +use PHPUnit\Framework\TestCase;
  7 +
  8 +/**
  9 + * Class BootTest
  10 + *
  11 + * @author wuzhc
  12 + * @internal
  13 + */
  14 +abstract class BootTest extends TestCase
  15 +{
  16 + const PID_FILE = __DIR__ . '/AukeySwrpc.pid';
  17 + const SERVER_LOG = __DIR__ . '/AukeySwrpc.log';
  18 + const SERVER_SCRIPT = __DIR__ . '/server.sh';
  19 +
  20 + public static function setUpBeforeClass(): void
  21 + {
  22 + // fwrite(STDOUT, 'Starting rpc server...' . PHP_EOL);
  23 + $cmd = 'nohup ' . self::SERVER_SCRIPT . ' > ' . self::SERVER_LOG . ' 2>&1 &';
  24 + shell_exec($cmd);
  25 + sleep(5);
  26 +
  27 + self::assertFileExists(self::PID_FILE, 'Run rpc server failed: ' . $cmd . '');
  28 + $pid = file_get_contents(self::PID_FILE);
  29 + self::assertNotEmpty($pid, 'Failed to start the rpc server.');
  30 +
  31 + $res = shell_exec('ps aux | grep ' . $pid . ' | wc -l');
  32 + self::assertGreaterThanOrEqual(1, intval($res), 'Failed to start the rpc server.');
  33 +
  34 + // fwrite(STDOUT, 'Rpc server started successfully.' . PHP_EOL);
  35 + }
  36 +
  37 + public static function tearDownAfterClass(): void
  38 + {
  39 + if (\file_exists(self::PID_FILE)) {
  40 + $pid = file_get_contents(self::PID_FILE);
  41 + \shell_exec('kill -15 ' . $pid);
  42 + if (\file_exists(self::PID_FILE)) {
  43 + \unlink(self::PID_FILE);
  44 + }
  45 + \sleep(1);
  46 + }
  47 + }
  48 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpcTests;
  4 +
  5 +
  6 +use AukeySwrpc\Register\Consul;
  7 +use AukeySwrpc\Register\Service;
  8 +use AukeySwrpc\Request;
  9 +use AukeySwrpc\Client;
  10 +
  11 +/**
  12 + * 客户端单元测试
  13 + * php74 ../phpunit.phar tests/ClientTest.php --debug
  14 + * Class ClientTest
  15 + *
  16 + * @link http://www.phpunit.cn/manual/7.0/zh_cn/index.html
  17 + * @author pengjch 2024312 9:39:22
  18 + */
  19 +class ClientTest extends BootTest
  20 +{
  21 + /**
  22 + * @return Client Client
  23 + * @author pengjch 2024312 14:29:28
  24 + */
  25 + public function testClientConnect(): Client
  26 + {
  27 + $client = Client::create('School_Module', getenv('RPC_SERVER_HOST'), getenv('RPC_SERVER_PORT'));
  28 + $conn = $client->connect();
  29 + $this->assertIsBool($conn->isConnected(), 'Client connect failure.');
  30 + return $client;
  31 + }
  32 +
  33 + /**
  34 + * @depends testClientConnect
  35 + * @param Client $client
  36 + * @author pengjch 2024312 14:29:20
  37 + */
  38 + public function testClientSyncRequest($client)
  39 + {
  40 + $request = Request\SyncRequest::create('AukeySwrpcTests\services\SchoolService_getUserSchool', [1, 1]);
  41 + $res = $client->send($request);
  42 + $this->assertEquals('未来学校1', $res);
  43 + }
  44 +
  45 + /**
  46 + * @depends testClientConnect
  47 + * @param $client
  48 + * @author pengjch 2024313 10:3:43
  49 + */
  50 + public function testClientAsyncRequest($client)
  51 + {
  52 + $request = Request\AsyncRequest::create('AukeySwrpcTests\services\SchoolService_saveUserName', ['tony']);
  53 + $res = $client->send($request);
  54 + $this->assertEquals('success', $res);
  55 + sleep(3);
  56 + $this->assertFileExists('xxx.log', 'Async request failure.');
  57 + $value = file_get_contents('xxx.log');
  58 + $this->assertEquals('tony', $value);
  59 + @unlink('xxx.log');
  60 + }
  61 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpcTests;
  4 +
  5 +
  6 +use PHPUnit\Framework\TestCase;
  7 +use AukeySwrpc\Request\SyncRequest;
  8 +
  9 +/**
  10 + * Class PackerTest
  11 + * php74 ../phpunit.phar tests/ClientTest.php
  12 + *
  13 + * @package AukeySwrpcTests
  14 + * @author pengjch 2024312 16:5:14
  15 + */
  16 +class PackerTest extends TestCase
  17 +{
  18 + /**
  19 + * 注意:Request类属性和方法发生变化时,这个测试案例就没有意义了
  20 + * @return string
  21 + * @author pengjch 2024312 14:57:32
  22 + */
  23 + public function testSerializeLengthPack()
  24 + {
  25 + $packer = new \AukeySwrpc\Packer\SerializeLengthPacker();
  26 + $result = $packer->pack(SyncRequest::create('SchoolService_getName', [1, 1]));
  27 + $this->assertEquals('AAAAzU86MjU6IlN3cnBjXFJlcXVlc3RcU3luY1JlcXVlc3QiOjY6e3M6OToiACoAbWV0aG9kIjtzOjIxOiJTY2hvb2xTZXJ2aWNlX2dldE5hbWUiO3M6OToiACoAcGFyYW1zIjthOjI6e2k6MDtpOjE7aToxO2k6MTt9czo5OiIAKgBpc1N5bmMiO2I6MTtzOjExOiIAKgBpc1N5c3RlbSI7YjowO3M6ODoiACoAZXJyb3IiO047czoxNToiACoAdHJhY2VDb250ZXh0IjtOO30=', base64_encode($result));
  28 + return base64_encode($result);
  29 + }
  30 +
  31 + /**
  32 + * @depends testSerializeLengthPack
  33 + * @author pengjch 2024312 14:57:23
  34 + */
  35 + public function testSerializeLenghtUnpack($value)
  36 + {
  37 + $expect = SyncRequest::create('SchoolService_getName', [1, 1]);
  38 + $packer = new \AukeySwrpc\Packer\SerializeLengthPacker();
  39 + $result = $packer->unpack(base64_decode($value));
  40 + $this->assertSame(serialize($expect), serialize($result));
  41 + }
  42 +
  43 + /**
  44 + * 注意:Request类属性和方法发生变化时,这个测试案例就没有意义了
  45 + * @return string
  46 + * @author pengjch 2024312 14:57:32
  47 + */
  48 + public function testSerializeEofPack()
  49 + {
  50 + $packer = new \AukeySwrpc\Packer\SerializeEofPacker();
  51 + $result = $packer->pack(SyncRequest::create('SchoolService_getName', [1, 1]));
  52 + $this->assertEquals('TzoyNToiU3dycGNcUmVxdWVzdFxTeW5jUmVxdWVzdCI6Njp7czo5OiIAKgBtZXRob2QiO3M6MjE6IlNjaG9vbFNlcnZpY2VfZ2V0TmFtZSI7czo5OiIAKgBwYXJhbXMiO2E6Mjp7aTowO2k6MTtpOjE7aToxO31zOjk6IgAqAGlzU3luYyI7YjoxO3M6MTE6IgAqAGlzU3lzdGVtIjtiOjA7czo4OiIAKgBlcnJvciI7TjtzOjE1OiIAKgB0cmFjZUNvbnRleHQiO047fQ==', base64_encode($result));
  53 + return base64_encode($result);
  54 + }
  55 +
  56 + /**
  57 + * @depends testSerializeEofPack
  58 + * @author pengjch 2024312 14:57:23
  59 + */
  60 + public function testSerializeEofUnpack($value)
  61 + {
  62 + $expect = SyncRequest::create('SchoolService_getName', [1, 1]);
  63 + $packer = new \AukeySwrpc\Packer\SerializeEofPacker();
  64 + $result = $packer->unpack(base64_decode($value));
  65 + $this->assertSame(serialize($expect), serialize($result));
  66 + }
  67 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpcTests;
  4 +
  5 +
  6 +use Monolog\Logger;
  7 +use AukeySwrpc\Register\Consul;
  8 +use AukeySwrpc\Register\Service;
  9 +use AukeySwrpc\Request\Request;
  10 +use AukeySwrpc\Request\SyncRequest;
  11 +use AukeySwrpc\Server;
  12 +use AukeySwrpcTests\services\UserService;
  13 +
  14 +/**
  15 + * Class ServerTest
  16 + * php74 ../phpunit.phar tests/ClientTest.php
  17 + *
  18 + * @package AukeySwrpcTests
  19 + * @author pengjch 2024312 16:5:7
  20 + */
  21 +class ServerTest extends \PHPUnit\Framework\TestCase
  22 +{
  23 + /**
  24 + * @author pengjch 2024312 17:8:31
  25 + */
  26 + public function testServerRegisterToConsul()
  27 + {
  28 + $res = shell_exec('netstat anp | grep ' . getenv('CONSUL_PORT') . ' | wc -l');
  29 + $this->assertGreaterThanOrEqual(1, intval($res), 'Warning: Consul not started.');
  30 +
  31 + $consul = new Consul('http://' . getenv('CONSUL_HOST') . ':' . getenv('CONSUL_PORT'));
  32 + $consul->register('test_module', '127.0.0.1', 8080);
  33 +
  34 + $isSuccess = false;
  35 + $services = $consul->getServices('test_module');
  36 + /** @var Service $service */
  37 + foreach ($services as $service) {
  38 + if ($service->getHost() == '127.0.0.1' && $service->getPort() == 8080) {
  39 + $isSuccess = true;
  40 + break;
  41 + }
  42 + }
  43 +
  44 + $this->assertIsBool($isSuccess);
  45 + return $consul;
  46 + }
  47 +
  48 + /**
  49 + * @depends testServerRegisterToConsul
  50 + * @param Consul $consul
  51 + * @author pengjch 2024312 17:12:17
  52 + */
  53 + public function testServerUnregisterFromConsul($consul)
  54 + {
  55 + $consul->unRegister('127.0.0.1', 8080);
  56 + $isSuccess = true;
  57 + $services = $consul->getServices('test_module');
  58 + /** @var Service $service */
  59 + foreach ($services as $service) {
  60 + if ($service->getHost() == '127.0.0.1' && $service->getPort() == 8080) {
  61 + $isSuccess = false;
  62 + break;
  63 + }
  64 + }
  65 + $this->assertIsBool($isSuccess);
  66 + }
  67 +
  68 + /**
  69 + * @return Server
  70 + * @author pengjch 2024312 17:8:17
  71 + */
  72 + public function testServerAddService()
  73 + {
  74 + $logger = new Logger('swprc');
  75 + $server = new Server('School_Module', getenv('RPC_SERVER_HOST'), getenv('RPC_SERVER_PORT'), [], null, null, $logger);
  76 + $server->addService(UserService::class);
  77 + $key = UserService::class . '_getName';
  78 + $value = $server->getService($key);
  79 + $this->assertEquals(UserService::class, $value);
  80 + return $server;
  81 + }
  82 +
  83 + /**
  84 + * @depends testServerAddService
  85 + * @param $server
  86 + * @author pengjch 2024312 16:40:0
  87 + */
  88 + public function testServerAddMiddleware($server)
  89 + {
  90 + $request = SyncRequest::create('AukeySwrpcTests\services\UserService_getFavoriteFood', ['肥胖']);
  91 + $server->addMiddleware(function (Request $request, $next) {
  92 + $request->setParams(['帅气']);
  93 + return $next($request);
  94 + });
  95 + $func = $server->getRequestHandler();
  96 + $result = $func($request);
  97 + $this->assertEquals('帅气的我喜欢吃苹果', $result->data['result']);
  98 + }
  99 +}
  1 +#!/usr/bin/env /opt/php74/bin/php
  2 +<?php
  3 +
  4 +use AukeySwrpc\Register\Consul;
  5 +use AukeySwrpc\Server;
  6 +
  7 +$basePath = dirname(dirname(__FILE__));
  8 +require_once $basePath . "/vendor/autoload.php";
  9 +
  10 +$options = [
  11 + 'enable_coroutine' => true,
  12 + 'pid_file' => __DIR__ . '/AukeySwrpc.pid',
  13 +];
  14 +$server = new Server('School_Module', getenv('RPC_SERVER_HOST'), getenv('RPC_SERVER_PORT'), $options);
  15 +$server->addRegister(new Consul())
  16 + ->addService(\AukeySwrpcTests\services\UserService::class)
  17 + ->addService(\AukeySwrpcTests\services\SchoolService::class)
  18 + ->addService(\AukeySwrpcTests\services\ClassService::class)
  19 + ->start();
  1 +<?php
  2 +
  3 +namespace AukeySwrpcTests\services;
  4 +
  5 +
  6 +use AukeySwrpc\Exceptions\RpcException;
  7 +use AukeySwrpc\LogicService;
  8 +use AukeySwrpc\Register\Consul;
  9 +use AukeySwrpc\Request\SyncRequest;
  10 +
  11 +/**
  12 + * Class ClassService
  13 + *
  14 + * @package AukeySwrpcTests\services
  15 + * @author pengjch 2024313 9:15:21
  16 + */
  17 +class ClassService extends LogicService
  18 +{
  19 + public function getUserClass($userID = 1): string
  20 + {
  21 + $register = new Consul();
  22 + try {
  23 + $classID = 111;
  24 + $client = \AukeySwrpc\Client::createBalancer('School_Module', $register, \AukeySwrpc\Client::STRATEGY_WEIGHT);
  25 + $result = $client->send(SyncRequest::create('SchoolService_getUserSchool', [
  26 + $userID,
  27 + $classID,
  28 + ], $this->getTracerContext(__FUNCTION__)));
  29 + } catch (RpcException $e) {
  30 + return $e->getMessage() . PHP_EOL;
  31 + }
  32 +
  33 + return '高一2班, school:' . $result;
  34 + }
  35 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpcTests\services;
  4 +
  5 +
  6 +use AukeySwrpc\LogicService;
  7 +
  8 +/**
  9 + * Class SchoolService
  10 + *
  11 + * @package AukeySwrpcTests\services
  12 + * @author pengjch 2024313 9:15:30
  13 + */
  14 +class SchoolService extends LogicService
  15 +{
  16 + public function getUserSchool($userID, $classID): string
  17 + {
  18 + return '未来学校' . $userID;
  19 + }
  20 +
  21 + public function saveUserName($name)
  22 + {
  23 + file_put_contents('xxx.log', $name);
  24 + }
  25 +}
  1 +<?php
  2 +
  3 +namespace AukeySwrpcTests\services;
  4 +
  5 +
  6 +use AukeySwrpc\Exceptions\RpcException;
  7 +use AukeySwrpc\LogicService;
  8 +use AukeySwrpc\Register\Consul;
  9 +use AukeySwrpc\Request\SyncRequest;
  10 +
  11 +/**
  12 + * Class UserService
  13 + *
  14 + * @package AukeySwrpcTests\services
  15 + * @author pengjch 2024313 9:15:52
  16 + */
  17 +class UserService extends LogicService
  18 +{
  19 + /**
  20 + * @return UserService
  21 + * @author pengjch 2024311 11:32:35
  22 + */
  23 + public static function factory()
  24 + {
  25 + return parent::factory();
  26 + }
  27 +
  28 + public function getName(): string
  29 + {
  30 + $register = new Consul();
  31 + try {
  32 + $userID = 1;
  33 + $client = \AukeySwrpc\Client::createBalancer('Class_Module', $register, \AukeySwrpc\Client::STRATEGY_WEIGHT);
  34 + $result = $client->send(SyncRequest::create('ClassService_getUserClass', [$userID], $this->getTracerContext(__FUNCTION__)));
  35 + } catch (RpcException $e) {
  36 + return $e->getMessage() . PHP_EOL;
  37 + }
  38 +
  39 + return 'user:wuzhc, class:' . $result;
  40 + }
  41 +
  42 + public function getAge(): int
  43 + {
  44 + return 30;
  45 + }
  46 +
  47 + public function getFavoriteFood($prefix)
  48 + {
  49 + return $prefix . '的我喜欢吃苹果';
  50 + }
  51 +}