初始化代码

This commit is contained in:
2025-12-22 14:34:25 +08:00
parent c2c5ae2fdd
commit a77dbc743f
1510 changed files with 213008 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
<?php
namespace think\swoole\rpc;
class Error implements \JsonSerializable
{
/**
* @var int
*/
protected $code = 0;
/**
* @var string
*/
protected $message = '';
/**
* @var mixed
*/
protected $data;
/**
* @param int $code
* @param string $message
* @param mixed $data
*
* @return Error
*/
public static function make(int $code, string $message, $data = null): self
{
$instance = new static();
$instance->code = $code;
$instance->message = $message;
$instance->data = $data;
return $instance;
}
/**
* @return int
*/
public function getCode(): int
{
return $this->code;
}
/**
* @return string
*/
public function getMessage(): string
{
return $this->message;
}
/**
* @return mixed
*/
public function getData()
{
return $this->data;
}
public function jsonSerialize()
{
return [
'code' => $this->code,
'message' => $this->message,
'data' => $this->data,
];
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace think\swoole\rpc;
use Exception;
use think\swoole\contract\rpc\ParserInterface;
class JsonParser implements ParserInterface
{
/**
* Json-rpc version
*/
const VERSION = '2.0';
const DELIMITER = "@";
/**
* @param Protocol $protocol
*
* @return string
*/
public function encode(Protocol $protocol): string
{
$interface = $protocol->getInterface();
$methodName = $protocol->getMethod();
$method = $interface . self::DELIMITER . $methodName;
$data = [
'jsonrpc' => self::VERSION,
'method' => $method,
'params' => $protocol->getParams(),
'id' => '',
];
$string = json_encode($data, JSON_UNESCAPED_UNICODE);
return $string;
}
/**
* @param string $string
*
* @return Protocol
*/
public function decode(string $string): Protocol
{
$data = json_decode($string, true);
$error = json_last_error();
if ($error != JSON_ERROR_NONE) {
throw new Exception(
sprintf('Data(%s) is not json format!', $string)
);
}
$method = $data['method'] ?? '';
$params = $data['params'] ?? [];
if (empty($method)) {
throw new Exception(
sprintf('Method(%s) cant not be empty!', $string)
);
}
$methodAry = explode(self::DELIMITER, $method);
if (count($methodAry) < 2) {
throw new Exception(
sprintf('Method(%s) is bad format!', $method)
);
}
[$interfaceClass, $methodName] = $methodAry;
if (empty($interfaceClass) || empty($methodName)) {
throw new Exception(
sprintf('Interface(%s) or Method(%s) can not be empty!', $interfaceClass, $method)
);
}
return Protocol::make($interfaceClass, $methodName, $params);
}
/**
* @param string $string
*
* @return mixed
*/
public function decodeResponse(string $string)
{
$data = json_decode($string, true);
if (array_key_exists('result', $data)) {
return $data['result'];
}
$code = $data['error']['code'] ?? 0;
$message = $data['error']['message'] ?? '';
$data = $data['error']['data'] ?? null;
return Error::make($code, $message, $data);
}
/**
* @param mixed $result
*
* @return string
*/
public function encodeResponse($result): string
{
$data = [
'jsonrpc' => self::VERSION,
];
if ($result instanceof Error) {
$data['error'] = $result;
} else {
$data['result'] = $result;
}
$string = json_encode($data);
return $string;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace think\swoole\rpc;
class Protocol
{
/**
* @var string
*/
private $interface = '';
/**
* @var string
*/
private $method = '';
/**
* @var array
*/
private $params = [];
/**
* Replace constructor
*
* @param string $interface
* @param string $method
* @param array $params
*
* @return Protocol
*/
public static function make(string $interface, string $method, array $params)
{
$instance = new static();
$instance->interface = $interface;
$instance->method = $method;
$instance->params = $params;
return $instance;
}
/**
* @return string
*/
public function getInterface(): string
{
return $this->interface;
}
/**
* @return string
*/
public function getMethod(): string
{
return $this->method;
}
/**
* @return array
*/
public function getParams(): array
{
return $this->params;
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace think\swoole\rpc\client;
use think\swoole\contract\rpc\ParserInterface;
use think\swoole\exception\RpcClientException;
/**
* Class Client
* @package think\swoole\rpc\client
*/
class Client
{
protected $host;
protected $port;
protected $timeout;
protected $options;
/** @var \Swoole\Coroutine\Client */
protected $handler;
public function __construct($host, $port, $timeout = 0.5, $options = [])
{
$this->host = $host;
$this->port = $port;
$this->timeout = $timeout;
$this->options = $options;
$this->connect();
}
public function sendAndRecv(string $data, bool $reconnect = false)
{
if ($reconnect) {
$this->connect();
}
try {
if (!$this->send($data)) {
throw new RpcClientException(swoole_strerror($this->handler->errCode), $this->handler->errCode);
}
$result = $this->handler->recv();
if ($result === false || empty($result)) {
throw new RpcClientException(swoole_strerror($this->handler->errCode), $this->handler->errCode);
}
return $result;
} catch (RpcClientException $e) {
if ($reconnect) {
throw $e;
}
return $this->sendAndRecv($data, true);
}
}
public function send($data)
{
return $this->handler->send($data . ParserInterface::EOF);
}
protected function connect()
{
$client = new \Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
$client->set([
'open_eof_check' => true,
'open_eof_split' => true,
'package_eof' => ParserInterface::EOF,
]);
if (!$client->connect($this->host, $this->port, $this->timeout)) {
throw new RpcClientException(
sprintf('Connect failed host=%s port=%d', $this->host, $this->port)
);
}
$this->handler = $client;
}
public function __destruct()
{
if ($this->handler) {
$this->handler->close();
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace think\swoole\rpc\client;
use think\swoole\concerns\InteractsWithPoolConnector;
/**
* Class Connection
* @package think\swoole\rpc\client
* @mixin Client
*/
class Connection
{
use InteractsWithPoolConnector;
}

View File

@@ -0,0 +1,57 @@
<?php
namespace think\swoole\rpc\client;
use Swoole\Coroutine\Channel;
use think\helper\Arr;
use think\swoole\concerns\InteractsWithPool;
class Pool
{
use InteractsWithPool;
protected $clients;
public function __construct($clients)
{
$this->clients = $clients;
}
protected function getPoolMaxActive($name)
{
return $this->getClientConfig($name, 'max_active', 3);
}
protected function getPoolMaxWaitTime($name)
{
return $this->getClientConfig($name, 'max_wait_time', 3);
}
public function getClientConfig($client, $name, $default = null)
{
return Arr::get($this->clients, $client . "." . $name, $default);
}
/**
* @param $name
* @return Connection
*/
public function connect($name)
{
return $this->getPoolConnection($name);
}
protected function buildPoolConnection($client, Channel $pool)
{
return new Connection($client, $pool);
}
protected function createPoolConnection(string $name)
{
$host = $this->getClientConfig($name, 'host', '127.0.0.1');
$port = $this->getClientConfig($name, 'port', 9000);
$timeout = $this->getClientConfig($name, 'timeout', 0.5);
return new Client($host, $port, $timeout);
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace think\swoole\rpc\client;
use InvalidArgumentException;
use Nette\PhpGenerator\Factory;
use Nette\PhpGenerator\PhpFile;
use ReflectionClass;
use RuntimeException;
use think\swoole\contract\rpc\ParserInterface;
use think\swoole\exception\RpcResponseException;
use think\swoole\rpc\Error;
use think\swoole\rpc\JsonParser;
use think\swoole\rpc\Protocol;
class Proxy
{
protected $client;
protected $interface;
/** @var Pool */
protected $pool;
/** @var ParserInterface */
protected $parser;
public function __construct(Pool $pool)
{
$this->pool = $pool;
$parserClass = $this->pool->getClientConfig($this->client, 'parser', JsonParser::class);
$this->parser = new $parserClass;
}
protected function proxyCall($method, $params)
{
$protocol = Protocol::make($this->interface, $method, $params);
$data = $this->parser->encode($protocol);
$client = $this->pool->connect($this->client);
$response = $client->sendAndRecv($data);
$client->release();
$result = $this->parser->decodeResponse($response);
if ($result instanceof Error) {
throw new RpcResponseException($result);
}
return $result;
}
public static function getClassName($interface)
{
if (!interface_exists($interface)) {
throw new InvalidArgumentException(
sprintf('%s must be exist interface!', $interface)
);
}
$name = constant($interface . "::RPC");
$proxyName = class_basename($interface) . "Service";
$className = "rpc\\service\\${name}\\{$proxyName}";
if (!class_exists($className, false)) {
$file = new PhpFile;
$namespace = $file->addNamespace("rpc\\service\\${name}");
$namespace->addUse(Proxy::class);
$namespace->addUse($interface);
$class = $namespace->addClass($proxyName);
$class->setExtends(Proxy::class);
$class->addImplement($interface);
$class->addProperty('client', $name);
$class->addProperty('interface', class_basename($interface));
$reflection = new ReflectionClass($interface);
foreach ($reflection->getMethods() as $methodRef) {
$method = (new Factory)->fromMethodReflection($methodRef);
$method->setBody("return \$this->proxyCall('{$methodRef->getName()}', func_get_args());");
$class->addMember($method);
}
if (function_exists('eval')) {
eval($file);
} else {
$proxyFile = sprintf('%s/%s.php', sys_get_temp_dir(), $proxyName);
$result = file_put_contents($proxyFile, $file);
if ($result === false) {
throw new RuntimeException(sprintf('Proxy file(%s) generate fail', $proxyFile));
}
require $proxyFile;
unlink($proxyFile);
}
}
return $className;
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace think\swoole\rpc\server;
use Exception;
use ReflectionClass;
use ReflectionMethod;
use ReflectionNamedType;
use Swoole\Server;
use think\App;
use think\swoole\contract\rpc\ParserInterface;
use think\swoole\rpc\Error;
use Throwable;
class Dispatcher
{
const ACTION_INTERFACE = '@action_interface';
/**
* Parser error
*/
const PARSER_ERROR = -32700;
/**
* Invalid Request
*/
const INVALID_REQUEST = -32600;
/**
* Method not found
*/
const METHOD_NOT_FOUND = -32601;
/**
* Invalid params
*/
const INVALID_PARAMS = -32602;
/**
* Internal error
*/
const INTERNAL_ERROR = -32603;
protected $app;
protected $parser;
protected $services;
protected $server;
public function __construct(App $app, ParserInterface $parser, Server $server, $services)
{
$this->app = $app;
$this->parser = $parser;
$this->server = $server;
$this->prepareServices($services);
}
/**
* 获取服务接口
* @param $services
* @throws \ReflectionException
*/
protected function prepareServices($services)
{
foreach ($services as $className) {
$reflectionClass = new ReflectionClass($className);
$interfaces = $reflectionClass->getInterfaceNames();
foreach ($interfaces as $interface) {
$this->services[class_basename($interface)] = [
'interface' => $interface,
'class' => $className,
];
}
}
}
/**
* 获取接口信息
* @return array
*/
protected function getInterfaces()
{
$interfaces = [];
foreach ($this->services as $key => ['interface' => $interface]) {
$interfaces[$key] = $this->getMethods($interface);
}
return $interfaces;
}
protected function getMethods($interface)
{
$methods = [];
$reflection = new ReflectionClass($interface);
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
$returnType = $method->getReturnType();
if ($returnType instanceof ReflectionNamedType) {
$returnType = $returnType->getName();
}
$methods[$method->getName()] = [
'parameters' => $this->getParameters($method),
'returnType' => $returnType,
'comment' => $method->getDocComment(),
];
}
return $methods;
}
protected function getParameters(ReflectionMethod $method)
{
$parameters = [];
foreach ($method->getParameters() as $parameter) {
$type = $parameter->getType();
if ($type instanceof ReflectionNamedType) {
$type = $type->getName();
}
$param = [
'name' => $parameter->getName(),
'type' => $type,
];
if ($parameter->isOptional()) {
$param['default'] = $parameter->getDefaultValue();
}
$parameters[] = $param;
}
return $parameters;
}
/**
* 调度
* @param int $fd
* @param string $data
*/
public function dispatch(int $fd, string $data)
{
try {
if ($data === Dispatcher::ACTION_INTERFACE) {
$result = $this->getInterfaces();
} else {
$protocol = $this->parser->decode($data);
$interface = $protocol->getInterface();
$method = $protocol->getMethod();
$params = $protocol->getParams();
$service = $this->services[$interface] ?? null;
if (empty($service)) {
throw new Exception(
sprintf('Service %s is not founded!', $interface),
self::INVALID_REQUEST
);
}
$result = $this->app->invoke([$this->app->make($service['class']), $method], $params);
}
} catch (Throwable | Exception $e) {
$result = Error::make($e->getCode(), $e->getMessage());
}
$data = $this->parser->encodeResponse($result);
$this->server->send($fd, $data . ParserInterface::EOF);
}
}