正在显示
35 个修改的文件
包含
2593 行增加
和
0 行删除
README.md
0 → 100755
| 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 | + | ||
| 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 | + | ||
| 205 | + | ||
| 206 | + | ||
| 207 | + | ||
| 208 | + | ||
| 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 | + | ||
| 249 | + | ||
| 250 | +如图,User_Module调用Class_Module,Class_Module又去调用School_Module | ||
| 251 | + | ||
| 252 | + | ||
| 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 | + | ||
| 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 | +``` |
composer.json
0 → 100755
| 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 | +} |
composer.lock
0 → 100755
此 diff 太大无法显示。
phpunit.xml
0 → 100755
| 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> |
src/AukeySwrpcProvider.php
0 → 100644
| 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 | +} |
src/Client.php
0 → 100755
| 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 | +} |
src/Event.php
0 → 100755
| 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 | +} |
src/Exceptions/RequestException.php
0 → 100755
src/Exceptions/RpcException.php
0 → 100755
src/Exceptions/TypeException.php
0 → 100755
src/LogicService.php
0 → 100755
| 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 | +} |
src/Middlewares/MiddlewareInterface.php
0 → 100755
| 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 | +} |
src/Middlewares/TraceMiddleware.php
0 → 100755
| 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 | +} |
src/Packer/PackerInterface.php
0 → 100755
| 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 | +} |
src/Packer/SerializeEofPacker.php
0 → 100755
| 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 | +} |
src/Packer/SerializeLengthPacker.php
0 → 100755
| 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 | +} |
src/Register/Consul.php
0 → 100755
| 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 | +} |
src/Register/RegisterInterface.php
0 → 100755
| 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 | +} |
src/Register/Service.php
0 → 100755
| 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 | +} |
src/Request/AsyncRequest.php
0 → 100755
| 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 | +} |
src/Request/Request.php
0 → 100755
| 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 | +} |
src/Request/SyncRequest.php
0 → 100755
| 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 | +} |
src/Request/SystemRequest.php
0 → 100755
| 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 | +} |
src/Response.php
0 → 100755
| 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 | +} |
src/Server.php
0 → 100755
| 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 | +} |
src/Service.php
0 → 100755
| 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 | +} |
src/Tracer/TracerContext.php
0 → 100755
| 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 | +} |
tests/BootTest.php
0 → 100755
| 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 | +} |
tests/ClientTest.php
0 → 100755
| 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 | +} |
tests/PackerTest.php
0 → 100755
| 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 | +} |
tests/ServerTest.php
0 → 100755
| 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 | +} |
tests/server.sh
0 → 100755
| 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(); |
tests/services/ClassService.php
0 → 100755
| 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 | +} |
tests/services/SchoolService.php
0 → 100755
| 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 | +} |
tests/services/UserService.php
0 → 100755
| 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 | +} |
-
请 注册 或 登录 后发表评论