├── .gitattributes ├── LICENSE ├── README.md ├── composer.json ├── publish └── nacos.php └── src ├── AbstractProvider.php ├── Application.php ├── ApplicationFactory.php ├── Config.php ├── ConfigProvider.php ├── Exception ├── ConnectToServerFailedException.php ├── InvalidArgumentException.php ├── NacosThrowable.php ├── RequestException.php └── RuntimeException.php ├── GrpcClient.php ├── GrpcFactory.php ├── Module.php ├── Protobuf ├── Any.php ├── GPBMetadata │ ├── Any.php │ └── Nacos.php ├── ListenContext.php ├── ListenHandler │ ├── ConfigChangeNotifyRequestHandler.php │ └── NamingPushRequestHandler.php ├── ListenHandlerInterface.php ├── Message │ ├── Instance.php │ └── Service.php ├── Metadata.php ├── Payload.php ├── Request │ ├── ConfigBatchListenRequest.php │ ├── ConfigQueryRequest.php │ ├── ConnectionSetupRequest.php │ ├── HealthCheckRequest.php │ ├── InstanceRequest.php │ ├── NamingRequest.php │ ├── NotifySubscriberResponse.php │ ├── Request.php │ ├── RequestInterface.php │ ├── ServerCheckRequest.php │ ├── ServiceQueryRequest.php │ └── SubscribeServiceRequest.php ├── Response │ ├── ConfigChangeBatchListenResponse.php │ ├── ConfigChangeNotifyRequest.php │ ├── ConfigQueryResponse.php │ ├── HealthCheckResponse.php │ ├── Mapping.php │ ├── NotifySubscriberRequest.php │ ├── Response.php │ ├── ServerCheckResponse.php │ └── SubscribeServiceResponse.php ├── ServiceHost.php ├── ServiceInfo.php ├── any.proto └── nacos.proto └── Provider ├── AccessToken.php ├── AuthProvider.php ├── ConfigProvider.php ├── InstanceProvider.php ├── OperatorProvider.php └── ServiceProvider.php /.gitattributes: -------------------------------------------------------------------------------- 1 | /tests export-ignore 2 | /.github export-ignore 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Hyperf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nacos SDK 2 | 3 | ## 安装 4 | 5 | ```shell 6 | composer require hyperf/nacos 7 | ``` 8 | 9 | ## 使用 10 | 11 | ```php 12 | 'nacos', 20 | 'password' => 'nacos', 21 | 'guzzle_config' => [ 22 | 'headers' => [ 23 | 'charset' => 'UTF-8', 24 | ], 25 | ], 26 | ])); 27 | 28 | $response = $application->auth->login('nacos', 'nacos'); 29 | $result = Json::decode((string) $response->getBody()); 30 | 31 | $response = $application->config->get('hyperf-service-config', 'DEFAULT_GROUP'); 32 | $result = Json::decode((string) $response->getBody()); 33 | ``` 34 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperf/nacos", 3 | "description": "Nacos SDK", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "php", 8 | "hyperf", 9 | "nacos" 10 | ], 11 | "require": { 12 | "php": ">=8.1", 13 | "guzzlehttp/guzzle": "^6.5 || ^7.0", 14 | "hyperf/codec": "~3.1.0", 15 | "hyperf/collection": "~3.1.0", 16 | "hyperf/context": "~3.1.0", 17 | "hyperf/contract": "~3.1.0", 18 | "hyperf/coordinator": "~3.1.0", 19 | "hyperf/engine": "~3.1.0", 20 | "hyperf/support": "~3.1.0", 21 | "jetbrains/phpstorm-attributes": "^1.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Hyperf\\Nacos\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "HyperfTest\\Nacos\\": "tests" 31 | } 32 | }, 33 | "config": { 34 | "sort-packages": true 35 | }, 36 | "extra": { 37 | "branch-alias": { 38 | "dev-master": "3.1-dev" 39 | }, 40 | "hyperf": { 41 | "config": "Hyperf\\Nacos\\ConfigProvider" 42 | } 43 | }, 44 | "suggests": { 45 | "google/protobuf": "Required to use nacos v2 grpc apis.", 46 | "hyperf/grpc": "Required to use nacos v2 grpc apis.", 47 | "hyperf/http2-client": "Required to use nacos v2 grpc apis." 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /publish/nacos.php: -------------------------------------------------------------------------------- 1 | 'http://127.0.0.1:8848/', 15 | // The nacos host info 16 | 'host' => '127.0.0.1', 17 | 'port' => 8848, 18 | // The nacos account info 19 | 'username' => null, 20 | 'password' => null, 21 | 'guzzle' => [ 22 | 'config' => null, 23 | ], 24 | // Only support for nacos v2. 25 | 'grpc' => [ 26 | 'enable' => false, 27 | 'heartbeat' => 10, 28 | ], 29 | ]; 30 | -------------------------------------------------------------------------------- /src/AbstractProvider.php: -------------------------------------------------------------------------------- 1 | config->getAccessKey()) { 34 | $accessSecret = $this->config->getAccessSecret(); 35 | 36 | if (str_contains($uri, '/ns/')) { // naming 37 | $options[RequestOptions::HEADERS]['ak'] = $accessKey; 38 | $signHeaders = $this->getNamingSignHeaders( 39 | $options[RequestOptions::QUERY]['groupName'] ?? '', 40 | $options[RequestOptions::QUERY]['serviceName'] ?? '', 41 | $accessSecret 42 | ); 43 | foreach ($signHeaders as $header => $value) { 44 | $options[RequestOptions::HEADERS][$header] = $value; 45 | } 46 | } else { // config 47 | $options[RequestOptions::HEADERS]['Spas-AccessKey'] = $accessKey; 48 | $signHeaders = $this->getMseSignHeaders($options[RequestOptions::QUERY] ?? [], $accessSecret); 49 | 50 | foreach ($signHeaders as $header => $value) { 51 | $options[RequestOptions::HEADERS][$header] = $value; 52 | } 53 | } 54 | } else { 55 | if ($token = $this->getAccessToken()) { 56 | $options[RequestOptions::QUERY]['accessToken'] = $token; 57 | } 58 | } 59 | 60 | return $this->client()->request($method, $uri, $options); 61 | } 62 | 63 | public function client(): Client 64 | { 65 | $config = array_merge($this->config->getGuzzleConfig(), [ 66 | 'base_uri' => $this->config->getBaseUri(), 67 | ]); 68 | 69 | return new Client($config); 70 | } 71 | 72 | protected function checkResponseIsOk(ResponseInterface $response): bool 73 | { 74 | if ($response->getStatusCode() !== 200) { 75 | return false; 76 | } 77 | 78 | return (string) $response->getBody() === 'ok'; 79 | } 80 | 81 | protected function handleResponse(ResponseInterface $response): array 82 | { 83 | $statusCode = $response->getStatusCode(); 84 | $contents = (string) $response->getBody(); 85 | 86 | if ($statusCode !== 200) { 87 | throw new RequestException($contents, $statusCode); 88 | } 89 | 90 | return Json::decode($contents); 91 | } 92 | 93 | protected function filter(array $input): array 94 | { 95 | $result = []; 96 | 97 | foreach ($input as $key => $value) { 98 | if ($value !== null) { 99 | $result[$key] = $value; 100 | } 101 | } 102 | 103 | return $result; 104 | } 105 | 106 | protected function getMseSignHeaders(array $data, string $secretKey): array 107 | { 108 | $group = $data['group'] ?? ''; 109 | $tenant = $data['tenant'] ?? ''; 110 | $timeStamp = round(microtime(true) * 1000); 111 | $signStr = ''; 112 | 113 | if ($tenant) { 114 | $signStr .= "{$tenant}+"; 115 | } 116 | 117 | if ($group) { 118 | $signStr .= "{$group}+"; 119 | } 120 | 121 | $signStr .= "{$timeStamp}"; 122 | 123 | return [ 124 | 'timeStamp' => $timeStamp, 125 | 'Spas-Signature' => base64_encode(hash_hmac('sha1', $signStr, $secretKey, true)), 126 | ]; 127 | } 128 | 129 | protected function getNamingSignHeaders(string $groupName, string $serverName, string $secretKey): array 130 | { 131 | $timeStamp = round(microtime(true) * 1000); 132 | $signStr = $timeStamp; 133 | 134 | if (! empty($serverName)) { 135 | if (str_contains($serverName, '@@') || empty($groupName)) { 136 | $signStr .= "@@{$serverName}"; 137 | } else { 138 | $signStr .= "@@{$groupName}@@{$serverName}"; 139 | } 140 | } 141 | 142 | return [ 143 | 'data' => $signStr, 144 | 'signature' => base64_encode(hash_hmac('sha1', $signStr, $secretKey, true)), 145 | ]; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | AuthProvider::class, 34 | 'config' => ConfigProvider::class, 35 | 'instance' => InstanceProvider::class, 36 | 'operator' => OperatorProvider::class, 37 | 'service' => ServiceProvider::class, 38 | 'grpc' => GrpcFactory::class, 39 | ]; 40 | 41 | protected array $providers = []; 42 | 43 | public function __construct(protected Config $config) 44 | { 45 | } 46 | 47 | public function __get($name) 48 | { 49 | if (! isset($name) || ! isset($this->alias[$name])) { 50 | throw new InvalidArgumentException("{$name} is invalid."); 51 | } 52 | 53 | if (isset($this->providers[$name])) { 54 | return $this->providers[$name]; 55 | } 56 | 57 | $class = $this->alias[$name]; 58 | return $this->providers[$name] = new $class($this, $this->config); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ApplicationFactory.php: -------------------------------------------------------------------------------- 1 | get(ConfigInterface::class)->get('nacos', []); 23 | if (! empty($config['uri'])) { 24 | $baseUri = $config['uri']; 25 | } else { 26 | $baseUri = sprintf('http://%s:%d', $config['host'] ?? '127.0.0.1', $config['port'] ?? 8848); 27 | } 28 | 29 | return new Application(new Config([ 30 | 'base_uri' => $baseUri, 31 | 'username' => $config['username'] ?? null, 32 | 'password' => $config['password'] ?? null, 33 | 'access_key' => $config['access_key'] ?? null, 34 | 'access_secret' => $config['access_secret'] ?? null, 35 | 'guzzle_config' => $config['guzzle']['config'] ?? null, 36 | 'host' => $config['host'] ?? null, 37 | 'port' => $config['port'] ?? null, 38 | ])); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | true, 35 | 'heartbeat' => 10, 36 | ]; 37 | 38 | protected array $guzzleConfig = [ 39 | 'headers' => [ 40 | 'charset' => 'UTF-8', 41 | ], 42 | 'http_errors' => false, 43 | ]; 44 | 45 | public function __construct( 46 | #[ArrayShape([ 47 | 'base_uri' => 'string', 48 | 'username' => 'string', 49 | 'password' => 'string', 50 | 'access_key' => 'string', 51 | 'access_secret' => 'string', 52 | 'guzzle_config' => 'array', 53 | 'host' => 'string', 54 | 'port' => 'int', 55 | ])] 56 | array $config = [] 57 | ) { 58 | isset($config['base_uri']) && $this->baseUri = (string) $config['base_uri']; 59 | isset($config['username']) && $this->username = (string) $config['username']; 60 | isset($config['password']) && $this->password = (string) $config['password']; 61 | isset($config['access_key']) && $this->accessKey = (string) $config['access_key']; 62 | isset($config['access_secret']) && $this->accessSecret = (string) $config['access_secret']; 63 | isset($config['guzzle_config']) && $this->guzzleConfig = (array) $config['guzzle_config']; 64 | isset($config['host']) && $this->host = (string) $config['host']; 65 | isset($config['port']) && $this->port = (int) $config['port']; 66 | isset($config['grpc']) && $this->grpc = array_replace($this->grpc, $config['grpc']); 67 | } 68 | 69 | public function getBaseUri(): string 70 | { 71 | return $this->baseUri; 72 | } 73 | 74 | public function getUsername(): ?string 75 | { 76 | return $this->username; 77 | } 78 | 79 | public function getPassword(): ?string 80 | { 81 | return $this->password; 82 | } 83 | 84 | public function getAccessKey(): ?string 85 | { 86 | return $this->accessKey; 87 | } 88 | 89 | public function getAccessSecret(): ?string 90 | { 91 | return $this->accessSecret; 92 | } 93 | 94 | public function getGuzzleConfig(): array 95 | { 96 | return $this->guzzleConfig; 97 | } 98 | 99 | public function getHost(): string 100 | { 101 | return $this->host; 102 | } 103 | 104 | public function getPort(): int 105 | { 106 | return $this->port; 107 | } 108 | 109 | public function getGrpc(): array 110 | { 111 | return $this->grpc; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | [ 21 | Application::class => ApplicationFactory::class, 22 | ], 23 | 'publish' => [ 24 | [ 25 | 'id' => 'nacos', 26 | 'description' => 'The config for nacos.', 27 | 'source' => __DIR__ . '/../publish/nacos.php', 28 | 'destination' => BASE_PATH . '/config/autoload/nacos.php', 29 | ], 30 | ], 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Exception/ConnectToServerFailedException.php: -------------------------------------------------------------------------------- 1 | 65 | */ 66 | protected array $configListenContexts = []; 67 | 68 | /** 69 | * @var array 70 | */ 71 | protected array $configListenHandlers = []; 72 | 73 | /** 74 | * @var array 75 | */ 76 | protected array $namingListenContexts = []; 77 | 78 | /** 79 | * @var array 80 | */ 81 | protected array $namingListenHandlers = []; 82 | 83 | protected int $streamId; 84 | 85 | public function __construct( 86 | protected Application $app, 87 | protected Config $config, 88 | protected ContainerInterface $container, 89 | protected string $namespaceId = '', 90 | protected string $module = 'config', 91 | ) { 92 | if ($this->container->has(StdoutLoggerInterface::class)) { 93 | $this->logger = $this->container->get(StdoutLoggerInterface::class); 94 | } 95 | 96 | $this->reconnect(); 97 | } 98 | 99 | public function request(RequestInterface $request, ?Client $client = null): Response 100 | { 101 | $payload = new Payload([ 102 | 'metadata' => new Metadata($this->getMetadata($request)), 103 | 'body' => new Any([ 104 | 'value' => Json::encode($request->getValue()), 105 | ]), 106 | ]); 107 | 108 | $client ??= $this->client; 109 | 110 | $response = $client->request( 111 | new Request('/Request/request', 'POST', Parser::serializeMessage($payload), $this->grpcDefaultHeaders()) 112 | ); 113 | 114 | return Response::jsonDeSerialize($response->getBody()); 115 | } 116 | 117 | public function write(int $streamId, RequestInterface $request, ?Client $client = null): bool 118 | { 119 | $payload = new Payload([ 120 | 'metadata' => new Metadata($this->getMetadata($request)), 121 | 'body' => new Any([ 122 | 'value' => Json::encode($request->getValue()), 123 | ]), 124 | ]); 125 | 126 | $client ??= $this->client; 127 | 128 | return $client->write($streamId, Parser::serializeMessage($payload)); 129 | } 130 | 131 | public function listenConfig(string $group, string $dataId, ListenHandlerInterface $callback, string $md5 = ''): void 132 | { 133 | $listenContext = new ListenContext($this->namespaceId, $group, $dataId, $md5); 134 | $this->configListenContexts[$listenContext->toKeyString()] = $listenContext; 135 | $this->configListenHandlers[$listenContext->toKeyString()] = $callback; 136 | } 137 | 138 | public function listenNaming(string $clusters, string $group, string $service, ListenHandlerInterface $callback): void 139 | { 140 | $serviceInfo = new ServiceInfo($service, $group, $clusters); 141 | if ($context = $this->namingListenContexts[$serviceInfo->toKeyString()] ?? null) { 142 | $callback->handle(new NotifySubscriberRequest(['requestId' => '', 'module' => 'naming', 'serviceInfo' => $context])); 143 | $this->namingListenHandlers[$serviceInfo->toKeyString()] = $callback; 144 | return; 145 | } 146 | 147 | $this->namingListenContexts[$serviceInfo->toKeyString()] = $serviceInfo; 148 | $this->namingListenHandlers[$serviceInfo->toKeyString()] = $callback; 149 | } 150 | 151 | public function listen(): void 152 | { 153 | $request = new ConfigBatchListenRequest(true, array_values($this->configListenContexts)); 154 | $response = $this->request($request); 155 | if ($response instanceof ConfigChangeBatchListenResponse) { 156 | $changedConfigs = $response->changedConfigs; 157 | foreach ($changedConfigs as $changedConfig) { 158 | $this->handleConfig($changedConfig->tenant, $changedConfig->group, $changedConfig->dataId); 159 | } 160 | } 161 | } 162 | 163 | protected function reconnect(): void 164 | { 165 | $this->client && $this->client->close(); 166 | $this->client = new Client( 167 | $this->config->getHost() . ':' . ($this->config->getPort() + 1000), 168 | [ 169 | 'heartbeat' => null, 170 | ] 171 | ); 172 | if ($this->logger) { 173 | $this->client->setLogger($this->logger); 174 | } 175 | 176 | $this->serverCheck(); 177 | $this->streamId = $this->bindStreamCall(); 178 | $this->healthCheck(); 179 | } 180 | 181 | protected function healthCheck() 182 | { 183 | go(function () { 184 | $client = $this->client; 185 | $heartbeat = $this->config->getGrpc()['heartbeat']; 186 | while ($heartbeat > 0 && $client->inLoop()) { 187 | if (CoordinatorManager::until(Constants::WORKER_EXIT)->yield($heartbeat)) { 188 | break; 189 | } 190 | $res = $this->request(new HealthCheckRequest(), $client); 191 | if ($res->errorCode !== 0) { 192 | $this->logger?->error('Health check failed, the result is ' . (string) $res); 193 | } 194 | } 195 | }); 196 | } 197 | 198 | protected function ip(): string 199 | { 200 | if ($this->container->has(IPReaderInterface::class)) { 201 | return $this->container->get(IPReaderInterface::class)->read(); 202 | } 203 | 204 | return Network::ip(); 205 | } 206 | 207 | protected function bindStreamCall(): int 208 | { 209 | $id = $this->client->send(new Request('/BiRequestStream/requestBiStream', 'POST', '', $this->grpcDefaultHeaders(), true)); 210 | go(function () use ($id) { 211 | $client = $this->client; 212 | while (true) { 213 | try { 214 | if (! $client->inLoop()) { 215 | break; 216 | } 217 | $response = $client->recv($id, -1); 218 | $response = Response::jsonDeSerialize($response->getBody()); 219 | match (true) { 220 | $response instanceof ConfigChangeNotifyRequest => $this->handleConfig( 221 | $response->tenant, 222 | $response->group, 223 | $response->dataId, 224 | $response 225 | ), 226 | $response instanceof NotifySubscriberRequest => $this->handleNaming($response), 227 | }; 228 | 229 | $this->listen(); 230 | } catch (Throwable $e) { 231 | ! $this->isWorkerExit() && $this->logger->error((string) $e); 232 | } 233 | } 234 | 235 | if (! $this->isWorkerExit()) { 236 | $this->reconnect(); 237 | $this->listen(); 238 | } 239 | }); 240 | 241 | $request = new ConnectionSetupRequest($this->namespaceId, $this->module); 242 | $this->write($id, $request); 243 | 244 | return $id; 245 | } 246 | 247 | protected function handleConfig(string $tenant, string $group, string $dataId, ?ConfigChangeNotifyRequest $request = null): void 248 | { 249 | $response = $this->request(new ConfigQueryRequest($tenant, $group, $dataId)); 250 | $key = ListenContext::getKeyString($tenant, $group, $dataId); 251 | if ($response instanceof ConfigQueryResponse) { 252 | if (isset($this->configListenContexts[$key])) { 253 | $this->configListenContexts[$key]->md5 = $response->getMd5(); 254 | $this->configListenHandlers[$key]?->handle($response); 255 | } 256 | 257 | if ($request && $ack = $this->configListenHandlers[$key]?->ack($request)) { 258 | $this->write($this->streamId, $ack); 259 | } 260 | } 261 | } 262 | 263 | protected function handleNaming(NotifySubscriberRequest $response): void 264 | { 265 | $serviceInfo = $response->serviceInfo; 266 | $key = $serviceInfo->toKeyString(); 267 | if (! isset($this->namingListenContexts[$key])) { 268 | $this->namingListenContexts[$key] = $serviceInfo; 269 | } 270 | 271 | if ($serviceInfo->lastRefTime > $this->namingListenContexts[$key]->lastRefTime) { 272 | $this->namingListenContexts[$key] = $serviceInfo; 273 | } 274 | 275 | if ($handler = $this->namingListenHandlers[$key] ?? null) { 276 | $handler->handle($response); 277 | $this->write($this->streamId, $handler->ack($response)); 278 | } else { 279 | $this->write($this->streamId, (new NamingPushRequestHandler(fn () => null))->ack($response)); 280 | } 281 | } 282 | 283 | /** 284 | * @deprecated since 3.1, use handleNaming instead. 285 | */ 286 | protected function hanldeNaming(NotifySubscriberRequest $response) 287 | { 288 | $this->handleNaming($response); 289 | } 290 | 291 | protected function serverCheck(): bool 292 | { 293 | $request = new ServerCheckRequest(); 294 | 295 | while (true) { 296 | try { 297 | $response = $this->request($request); 298 | if ($response->errorCode !== 0) { 299 | $this->logger?->error('Nacos check server failed.'); 300 | if (CoordinatorManager::until(Constants::WORKER_EXIT)->yield(5)) { 301 | break; 302 | } 303 | continue; 304 | } 305 | 306 | return true; 307 | } catch (Exception $exception) { 308 | $this->logger?->error((string) $exception); 309 | if (CoordinatorManager::until(Constants::WORKER_EXIT)->yield(5)) { 310 | break; 311 | } 312 | } 313 | } 314 | 315 | throw new ConnectToServerFailedException('the nacos server is not ready to work in 30 seconds, connect to server failed'); 316 | } 317 | 318 | private function isWorkerExit(): bool 319 | { 320 | return CoordinatorManager::until(Constants::WORKER_EXIT)->isClosing(); 321 | } 322 | 323 | private function getMetadata(RequestInterface $request): array 324 | { 325 | if ($token = $this->getAccessToken()) { 326 | return [ 327 | 'type' => $request->getType(), 328 | 'clientIp' => $this->ip(), 329 | 'headers' => [ 330 | 'accessToken' => $token, 331 | ], 332 | ]; 333 | } 334 | 335 | return [ 336 | 'type' => $request->getType(), 337 | 'clientIp' => $this->ip(), 338 | ]; 339 | } 340 | 341 | private function grpcDefaultHeaders(): array 342 | { 343 | return [ 344 | 'content-type' => 'application/grpc+proto', 345 | 'te' => 'trailers', 346 | 'user-agent' => 'Nacos-Hyperf-Client:v3.1', 347 | ]; 348 | } 349 | 350 | private function handleResponse(ResponseInterface $response): array 351 | { 352 | $statusCode = $response->getStatusCode(); 353 | $contents = (string) $response->getBody(); 354 | 355 | if ($statusCode !== 200) { 356 | throw new RequestException($contents, $statusCode); 357 | } 358 | 359 | return Json::decode($contents); 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /src/GrpcFactory.php: -------------------------------------------------------------------------------- 1 | > 23 | */ 24 | protected array $clients = []; 25 | 26 | public function __construct(protected Application $app, protected Config $config) 27 | { 28 | if (! $this->config->getGrpc()['enable']) { 29 | throw new InvalidArgumentException('GRPC module is disable, please set `nacos.default.grpc.enable = true`.'); 30 | } 31 | } 32 | 33 | public function get(string $namespaceId, Module|string $module = 'config'): GrpcClient 34 | { 35 | $module instanceof Module && $module = $module->value; 36 | 37 | if (isset($this->clients[$namespaceId][$module])) { 38 | return $this->clients[$namespaceId][$module]; 39 | } 40 | 41 | return $this->clients[$namespaceId][$module] = new GrpcClient($this->app, $this->config, $this->container(), $namespaceId, $module); 42 | } 43 | 44 | /** 45 | * @return array> array> 46 | */ 47 | public function getClients(): array 48 | { 49 | return $this->clients; 50 | } 51 | 52 | /** 53 | * @param string $module config or naming 54 | * @return array array 55 | */ 56 | public function moduleClients(Module|string $module): array 57 | { 58 | $module instanceof Module && $module = $module->value; 59 | 60 | $result = []; 61 | foreach ($this->clients as $namespaceId => $clients) { 62 | foreach ($clients as $key => $client) { 63 | if ($key === $module) { 64 | $result[$namespaceId] = $client; 65 | } 66 | } 67 | } 68 | 69 | return $result; 70 | } 71 | 72 | private function container(): ContainerInterface 73 | { 74 | return ApplicationContext::getContainer(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Module.php: -------------------------------------------------------------------------------- 1 | , 73 | * "lastName": 74 | * } 75 | * If the embedded message type is well-known and has a custom JSON 76 | * representation, that representation will be embedded adding a field 77 | * `value` which holds the custom JSON in addition to the `@type` 78 | * field. Example (for message [google.protobuf.Duration][]): 79 | * { 80 | * "@type": "type.googleapis.com/google.protobuf.Duration", 81 | * "value": "1.212s" 82 | * }. 83 | * 84 | * Generated from protobuf message google.protobuf.Any 85 | */ 86 | class Any extends AnyBase 87 | { 88 | /** 89 | * A URL/resource name that uniquely identifies the type of the serialized 90 | * protocol buffer message. This string must contain at least 91 | * one "/" character. The last segment of the URL's path must represent 92 | * the fully qualified name of the type (as in 93 | * `path/google.protobuf.Duration`). The name should be in a canonical form 94 | * (e.g., leading "." is not accepted). 95 | * In practice, teams usually precompile into the binary all types that they 96 | * expect it to use in the context of Any. However, for URLs which use the 97 | * scheme `http`, `https`, or no scheme, one can optionally set up a type 98 | * server that maps type URLs to message definitions as follows: 99 | * * If no scheme is provided, `https` is assumed. 100 | * * An HTTP GET on the URL must yield a [google.protobuf.Type][] 101 | * value in binary format, or produce an error. 102 | * * Applications are allowed to cache lookup results based on the 103 | * URL, or have them precompiled into a binary to avoid any 104 | * lookup. Therefore, binary compatibility needs to be preserved 105 | * on changes to types. (Use versioned type names to manage 106 | * breaking changes.) 107 | * Note: this functionality is not currently available in the official 108 | * protobuf release, and it is not used for type URLs beginning with 109 | * type.googleapis.com. 110 | * Schemes other than `http`, `https` (or the empty scheme) might be 111 | * used with implementation specific semantics. 112 | * 113 | * Generated from protobuf field string type_url = 1; 114 | */ 115 | protected $type_url = ''; 116 | 117 | /** 118 | * Must be a valid serialized protocol buffer of the above specified type. 119 | * 120 | * Generated from protobuf field bytes value = 2; 121 | */ 122 | protected $value = ''; 123 | 124 | /** 125 | * Constructor. 126 | * 127 | * @param array $data { 128 | * Optional. Data for populating the Message object. 129 | * 130 | * @var string $type_url 131 | * A URL/resource name that uniquely identifies the type of the serialized 132 | * protocol buffer message. This string must contain at least 133 | * one "/" character. The last segment of the URL's path must represent 134 | * the fully qualified name of the type (as in 135 | * `path/google.protobuf.Duration`). The name should be in a canonical form 136 | * (e.g., leading "." is not accepted). 137 | * In practice, teams usually precompile into the binary all types that they 138 | * expect it to use in the context of Any. However, for URLs which use the 139 | * scheme `http`, `https`, or no scheme, one can optionally set up a type 140 | * server that maps type URLs to message definitions as follows: 141 | * * If no scheme is provided, `https` is assumed. 142 | * * An HTTP GET on the URL must yield a [google.protobuf.Type][] 143 | * value in binary format, or produce an error. 144 | * * Applications are allowed to cache lookup results based on the 145 | * URL, or have them precompiled into a binary to avoid any 146 | * lookup. Therefore, binary compatibility needs to be preserved 147 | * on changes to types. (Use versioned type names to manage 148 | * breaking changes.) 149 | * Note: this functionality is not currently available in the official 150 | * protobuf release, and it is not used for type URLs beginning with 151 | * type.googleapis.com. 152 | * Schemes other than `http`, `https` (or the empty scheme) might be 153 | * used with implementation specific semantics. 154 | * @var string $value 155 | * Must be a valid serialized protocol buffer of the above specified type. 156 | * } 157 | */ 158 | public function __construct($data = null) 159 | { 160 | GPBMetadata\Any::initOnce(); 161 | parent::__construct($data); 162 | } 163 | 164 | /** 165 | * A URL/resource name that uniquely identifies the type of the serialized 166 | * protocol buffer message. This string must contain at least 167 | * one "/" character. The last segment of the URL's path must represent 168 | * the fully qualified name of the type (as in 169 | * `path/google.protobuf.Duration`). The name should be in a canonical form 170 | * (e.g., leading "." is not accepted). 171 | * In practice, teams usually precompile into the binary all types that they 172 | * expect it to use in the context of Any. However, for URLs which use the 173 | * scheme `http`, `https`, or no scheme, one can optionally set up a type 174 | * server that maps type URLs to message definitions as follows: 175 | * * If no scheme is provided, `https` is assumed. 176 | * * An HTTP GET on the URL must yield a [google.protobuf.Type][] 177 | * value in binary format, or produce an error. 178 | * * Applications are allowed to cache lookup results based on the 179 | * URL, or have them precompiled into a binary to avoid any 180 | * lookup. Therefore, binary compatibility needs to be preserved 181 | * on changes to types. (Use versioned type names to manage 182 | * breaking changes.) 183 | * Note: this functionality is not currently available in the official 184 | * protobuf release, and it is not used for type URLs beginning with 185 | * type.googleapis.com. 186 | * Schemes other than `http`, `https` (or the empty scheme) might be 187 | * used with implementation specific semantics. 188 | * 189 | * Generated from protobuf field string type_url = 1; 190 | * @return string 191 | */ 192 | public function getTypeUrl() 193 | { 194 | return $this->type_url; 195 | } 196 | 197 | /** 198 | * A URL/resource name that uniquely identifies the type of the serialized 199 | * protocol buffer message. This string must contain at least 200 | * one "/" character. The last segment of the URL's path must represent 201 | * the fully qualified name of the type (as in 202 | * `path/google.protobuf.Duration`). The name should be in a canonical form 203 | * (e.g., leading "." is not accepted). 204 | * In practice, teams usually precompile into the binary all types that they 205 | * expect it to use in the context of Any. However, for URLs which use the 206 | * scheme `http`, `https`, or no scheme, one can optionally set up a type 207 | * server that maps type URLs to message definitions as follows: 208 | * * If no scheme is provided, `https` is assumed. 209 | * * An HTTP GET on the URL must yield a [google.protobuf.Type][] 210 | * value in binary format, or produce an error. 211 | * * Applications are allowed to cache lookup results based on the 212 | * URL, or have them precompiled into a binary to avoid any 213 | * lookup. Therefore, binary compatibility needs to be preserved 214 | * on changes to types. (Use versioned type names to manage 215 | * breaking changes.) 216 | * Note: this functionality is not currently available in the official 217 | * protobuf release, and it is not used for type URLs beginning with 218 | * type.googleapis.com. 219 | * Schemes other than `http`, `https` (or the empty scheme) might be 220 | * used with implementation specific semantics. 221 | * 222 | * Generated from protobuf field string type_url = 1; 223 | * @param string $var 224 | * @return $this 225 | */ 226 | public function setTypeUrl($var) 227 | { 228 | GPBUtil::checkString($var, true); 229 | $this->type_url = $var; 230 | 231 | return $this; 232 | } 233 | 234 | /** 235 | * Must be a valid serialized protocol buffer of the above specified type. 236 | * 237 | * Generated from protobuf field bytes value = 2; 238 | * @return string 239 | */ 240 | public function getValue() 241 | { 242 | return $this->value; 243 | } 244 | 245 | /** 246 | * Must be a valid serialized protocol buffer of the above specified type. 247 | * 248 | * Generated from protobuf field bytes value = 2; 249 | * @param string $var 250 | * @return $this 251 | */ 252 | public function setValue($var) 253 | { 254 | GPBUtil::checkString($var, false); 255 | $this->value = $var; 256 | 257 | return $this; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/Protobuf/GPBMetadata/Any.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperf/nacos/3633d43fd30927a8c51b2ffac5dc211537c04d61/src/Protobuf/GPBMetadata/Any.php -------------------------------------------------------------------------------- /src/Protobuf/GPBMetadata/Nacos.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperf/nacos/3633d43fd30927a8c51b2ffac5dc211537c04d61/src/Protobuf/GPBMetadata/Nacos.php -------------------------------------------------------------------------------- /src/Protobuf/ListenContext.php: -------------------------------------------------------------------------------- 1 | $this->tenant, 28 | 'group' => $this->group, 29 | 'dataId' => $this->dataId, 30 | 'md5' => $this->md5, 31 | ]; 32 | } 33 | 34 | public static function jsonDeSerialize(mixed $data): static 35 | { 36 | return new static( 37 | $data['tenant'], 38 | $data['group'], 39 | $data['dataId'], 40 | $data['md5'] ?? '', 41 | ); 42 | } 43 | 44 | public function toKeyString(): string 45 | { 46 | return self::getKeyString($this->tenant, $this->group, $this->dataId); 47 | } 48 | 49 | public static function getKeyString(string $tenant, string $group, string $dataId): string 50 | { 51 | return sprintf('%s@@%s@@%s', $dataId, $group, $tenant); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Protobuf/ListenHandler/ConfigChangeNotifyRequestHandler.php: -------------------------------------------------------------------------------- 1 | callback; 30 | $callback($response); 31 | } 32 | 33 | public function ack(Response $response): Request 34 | { 35 | return new NotifySubscriberResponse($response->requestId); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Protobuf/ListenHandler/NamingPushRequestHandler.php: -------------------------------------------------------------------------------- 1 | callback; 30 | $callback($response); 31 | } 32 | 33 | public function ack(Response $response): Request 34 | { 35 | return new NotifySubscriberResponse($response->requestId); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Protobuf/ListenHandlerInterface.php: -------------------------------------------------------------------------------- 1 | $this->ip, 41 | 'port' => $this->port, 42 | 'weight' => $this->weight, 43 | 'enabled' => $this->enabled, 44 | 'healthy' => $this->healthy, 45 | 'clusterName' => $this->clusterName, 46 | 'ephemeral' => $this->ephemeral, 47 | 'metadata' => (object) $this->metadata, 48 | 'serviceName' => $this->serviceName, 49 | 'instanceId' => $this->instanceId, 50 | 'instanceHeartBeatInterval' => $this->instanceHeartBeatInterval, 51 | 'ipDeleteTimeout' => $this->ipDeleteTimeout, 52 | 'instanceHeartBeatTimeOut' => $this->instanceHeartBeatTimeOut, 53 | ]; 54 | } 55 | 56 | public static function jsonDeSerialize(mixed $data): static 57 | { 58 | return new static( 59 | $data['ip'], 60 | $data['port'], 61 | $data['weight'], 62 | $data['enabled'], 63 | $data['healthy'], 64 | $data['clusterName'], 65 | $data['ephemeral'], 66 | $data['metadata'], 67 | $data['serviceName'], 68 | $data['instanceId'], 69 | $data['instanceHeartBeatInterval'], 70 | $data['ipDeleteTimeout'], 71 | $data['instanceHeartBeatTimeOut'], 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Protobuf/Message/Service.php: -------------------------------------------------------------------------------- 1 | Metadata. 23 | */ 24 | class Metadata extends Message 25 | { 26 | /** 27 | * Generated from protobuf field string type = 3;. 28 | */ 29 | protected $type = ''; 30 | 31 | /** 32 | * Generated from protobuf field string clientIp = 8;. 33 | */ 34 | protected $clientIp = ''; 35 | 36 | /** 37 | * Generated from protobuf field map headers = 7;. 38 | */ 39 | private $headers; 40 | 41 | /** 42 | * Constructor. 43 | * 44 | * @param array $data { 45 | * Optional. Data for populating the Message object. 46 | * 47 | * @var string $type 48 | * @var string $clientIp 49 | * @var array|MapField $headers 50 | * } 51 | */ 52 | public function __construct($data = null) 53 | { 54 | GPBMetadata\Nacos::initOnce(); 55 | parent::__construct($data); 56 | } 57 | 58 | /** 59 | * Generated from protobuf field string type = 3;. 60 | * @return string 61 | */ 62 | public function getType() 63 | { 64 | return $this->type; 65 | } 66 | 67 | /** 68 | * Generated from protobuf field string type = 3;. 69 | * @param string $var 70 | * @return $this 71 | */ 72 | public function setType($var) 73 | { 74 | GPBUtil::checkString($var, true); 75 | $this->type = $var; 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * Generated from protobuf field string clientIp = 8;. 82 | * @return string 83 | */ 84 | public function getClientIp() 85 | { 86 | return $this->clientIp; 87 | } 88 | 89 | /** 90 | * Generated from protobuf field string clientIp = 8;. 91 | * @param string $var 92 | * @return $this 93 | */ 94 | public function setClientIp($var) 95 | { 96 | GPBUtil::checkString($var, true); 97 | $this->clientIp = $var; 98 | 99 | return $this; 100 | } 101 | 102 | /** 103 | * Generated from protobuf field map headers = 7;. 104 | * @return MapField 105 | */ 106 | public function getHeaders() 107 | { 108 | return $this->headers; 109 | } 110 | 111 | /** 112 | * Generated from protobuf field map headers = 7;. 113 | * @param array|MapField $var 114 | * @return $this 115 | */ 116 | public function setHeaders($var) 117 | { 118 | $arr = GPBUtil::checkMapField($var, GPBType::STRING, GPBType::STRING); 119 | $this->headers = $arr; 120 | 121 | return $this; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Protobuf/Payload.php: -------------------------------------------------------------------------------- 1 | Payload. 21 | */ 22 | class Payload extends Message 23 | { 24 | /** 25 | * Generated from protobuf field .Metadata metadata = 2;. 26 | */ 27 | protected $metadata; 28 | 29 | /** 30 | * Generated from protobuf field .google.protobuf.Any body = 3;. 31 | */ 32 | protected $body; 33 | 34 | /** 35 | * Constructor. 36 | * 37 | * @param array $data { 38 | * Optional. Data for populating the Message object. 39 | * 40 | * @var Metadata $metadata 41 | * @var Any $body 42 | * } 43 | */ 44 | public function __construct($data = null) 45 | { 46 | GPBMetadata\Nacos::initOnce(); 47 | parent::__construct($data); 48 | } 49 | 50 | /** 51 | * Generated from protobuf field .Metadata metadata = 2;. 52 | * @return null|Metadata 53 | */ 54 | public function getMetadata() 55 | { 56 | return $this->metadata; 57 | } 58 | 59 | public function hasMetadata() 60 | { 61 | return isset($this->metadata); 62 | } 63 | 64 | public function clearMetadata() 65 | { 66 | unset($this->metadata); 67 | } 68 | 69 | /** 70 | * Generated from protobuf field .Metadata metadata = 2;. 71 | * @param Metadata $var 72 | * @return $this 73 | */ 74 | public function setMetadata($var) 75 | { 76 | GPBUtil::checkMessage($var, Metadata::class); 77 | $this->metadata = $var; 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * Generated from protobuf field .google.protobuf.Any body = 3;. 84 | * @return null|Any 85 | */ 86 | public function getBody() 87 | { 88 | return $this->body; 89 | } 90 | 91 | public function hasBody() 92 | { 93 | return isset($this->body); 94 | } 95 | 96 | public function clearBody() 97 | { 98 | unset($this->body); 99 | } 100 | 101 | /** 102 | * Generated from protobuf field .google.protobuf.Any body = 3;. 103 | * @param Any $var 104 | * @return $this 105 | */ 106 | public function setBody($var) 107 | { 108 | GPBUtil::checkMessage($var, Any::class); 109 | $this->body = $var; 110 | 111 | return $this; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Protobuf/Request/ConfigBatchListenRequest.php: -------------------------------------------------------------------------------- 1 | $this->listen, 30 | 'module' => 'config', 31 | 'configListenContexts' => $this->configListenContexts, 32 | ]; 33 | } 34 | 35 | public function getType(): string 36 | { 37 | return 'ConfigBatchListenRequest'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Protobuf/Request/ConfigQueryRequest.php: -------------------------------------------------------------------------------- 1 | $this->tenant, 25 | 'group' => $this->group, 26 | 'dataId' => $this->dataId, 27 | 'module' => 'config', 28 | ]; 29 | } 30 | 31 | public function getType(): string 32 | { 33 | return 'ConfigQueryRequest'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Protobuf/Request/ConnectionSetupRequest.php: -------------------------------------------------------------------------------- 1 | $this->tenant, 25 | 'clientVersion' => 'Nacos-Hyperf-Client:v3.1', 26 | 'labels' => [ 27 | 'source' => 'sdk', 28 | 'AppName' => '', 29 | 'taskId' => '0', 30 | 'module' => $this->module, 31 | ], 32 | 'module' => 'internal', 33 | ]; 34 | } 35 | 36 | public function getType(): string 37 | { 38 | return 'ConnectionSetupRequest'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Protobuf/Request/HealthCheckRequest.php: -------------------------------------------------------------------------------- 1 | 'internal', 21 | ]; 22 | } 23 | 24 | public function getType(): string 25 | { 26 | return 'HealthCheckRequest'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Protobuf/Request/InstanceRequest.php: -------------------------------------------------------------------------------- 1 | request->toArray(), [ 32 | 'type' => $this->type, 33 | 'instance' => $this->instance, 34 | ]); 35 | } 36 | 37 | public function getType(): string 38 | { 39 | return 'InstanceRequest'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Protobuf/Request/NamingRequest.php: -------------------------------------------------------------------------------- 1 | $this->namespace, 27 | 'serviceName' => $this->serviceName, 28 | 'groupName' => $this->groupName, 29 | 'module' => 'naming', 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Protobuf/Request/NotifySubscriberResponse.php: -------------------------------------------------------------------------------- 1 | $this->resultCode, 25 | 'requestId' => $this->requestId, 26 | ]; 27 | } 28 | 29 | public function getType(): string 30 | { 31 | return 'NotifySubscriberResponse'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Protobuf/Request/Request.php: -------------------------------------------------------------------------------- 1 | 'utf-8', 22 | 'exConfigInfo' => 'true', 23 | 'Client-RequestToken' => md5($time), 24 | 'Client-RequestTS' => $time, 25 | 'Timestamp' => $time, 26 | // 'Spas-Signature' => 'PZqVeU8aUslLyV6tkuAG6qgjLKI=', 27 | 'Client-AppName' => '', 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Protobuf/Request/RequestInterface.php: -------------------------------------------------------------------------------- 1 | 'internal', 21 | ]; 22 | } 23 | 24 | public function getType(): string 25 | { 26 | return 'ServerCheckRequest'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Protobuf/Request/ServiceQueryRequest.php: -------------------------------------------------------------------------------- 1 | request->toArray(), [ 24 | 'cluster' => $this->cluster, 25 | 'healthyOnly' => $this->healthyOnly, 26 | 'udpPort' => $this->udpPort, 27 | ]); 28 | } 29 | 30 | public function getType(): string 31 | { 32 | return 'ServiceQueryRequest'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Protobuf/Request/SubscribeServiceRequest.php: -------------------------------------------------------------------------------- 1 | request->toArray(), [ 24 | 'clusters' => $this->clusters, 25 | 'subscribe' => $this->subscribe, 26 | ]); 27 | } 28 | 29 | public function getType(): string 30 | { 31 | return 'SubscribeServiceRequest'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Protobuf/Response/ConfigChangeBatchListenResponse.php: -------------------------------------------------------------------------------- 1 | changedConfigs[] = ListenContext::jsonDeSerialize($value); 28 | } 29 | 30 | parent::__construct(...parent::namedParameters($json)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Protobuf/Response/ConfigChangeNotifyRequest.php: -------------------------------------------------------------------------------- 1 | requestId = $json['requestId']; 31 | $this->tenant = $json['tenant']; 32 | $this->group = $json['group']; 33 | $this->dataId = $json['dataId']; 34 | $this->json = $json; 35 | } 36 | 37 | public function __toString(): string 38 | { 39 | return Json::encode($this->json); 40 | } 41 | 42 | public function jsonSerialize(): mixed 43 | { 44 | return $this->json; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Protobuf/Response/ConfigQueryResponse.php: -------------------------------------------------------------------------------- 1 | content = $json['content']; 32 | $this->encryptedDataKey = $json['encryptedDataKey']; 33 | $this->contentType = $json['contentType']; 34 | $this->md5 = $json['md5']; 35 | $this->lastModified = $json['lastModified']; 36 | $this->beta = $json['beta']; 37 | 38 | parent::__construct(...parent::namedParameters($json)); 39 | } 40 | 41 | public function getContent(): string 42 | { 43 | return $this->content; 44 | } 45 | 46 | public function getEncryptedDataKey(): string 47 | { 48 | return $this->encryptedDataKey; 49 | } 50 | 51 | public function getContentType(): string 52 | { 53 | return $this->contentType; 54 | } 55 | 56 | public function getMd5(): string 57 | { 58 | return $this->md5; 59 | } 60 | 61 | public function getLastModified(): int 62 | { 63 | return $this->lastModified; 64 | } 65 | 66 | public function isBeta(): bool 67 | { 68 | return $this->beta; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Protobuf/Response/HealthCheckResponse.php: -------------------------------------------------------------------------------- 1 | ServerCheckResponse::class, 19 | 'HealthCheckResponse' => HealthCheckResponse::class, 20 | 'ConfigChangeBatchListenResponse' => ConfigChangeBatchListenResponse::class, 21 | 'ConfigQueryResponse' => ConfigQueryResponse::class, 22 | 'ConfigChangeNotifyRequest' => ConfigChangeNotifyRequest::class, 23 | 'SubscribeServiceResponse' => SubscribeServiceResponse::class, 24 | 'NotifySubscriberRequest' => NotifySubscriberRequest::class, 25 | ]; 26 | } 27 | -------------------------------------------------------------------------------- /src/Protobuf/Response/NotifySubscriberRequest.php: -------------------------------------------------------------------------------- 1 | requestId = $json['requestId']; 27 | $this->module = $json['module']; 28 | $this->serviceInfo = is_array($json['serviceInfo']) ? ServiceInfo::jsonDeSerialize($json['serviceInfo']) : $json['serviceInfo']; 29 | } 30 | 31 | public function jsonSerialize(): mixed 32 | { 33 | return $this->json; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Protobuf/Response/Response.php: -------------------------------------------------------------------------------- 1 | getBody()->getValue()); 44 | $class = Mapping::$mappings[$payload->getMetadata()->getType()] ?? null; 45 | if (! $class) { 46 | return new static(...self::namedParameters($json)); 47 | } 48 | 49 | /* @phpstan-ignore-next-line */ 50 | return new $class($json); 51 | } 52 | 53 | protected static function namedParameters(array $data): array 54 | { 55 | return Arr::only($data, ['resultCode', 'errorCode', 'success', 'message', 'requestId']); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Protobuf/Response/ServerCheckResponse.php: -------------------------------------------------------------------------------- 1 | connectionId = $json['connectionId']; 22 | 23 | parent::__construct(...parent::namedParameters($json)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Protobuf/Response/SubscribeServiceResponse.php: -------------------------------------------------------------------------------- 1 | service = Service::jsonDeSerialize($json['serviceInfo']); 24 | 25 | parent::__construct(...parent::namedParameters($json)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Protobuf/ServiceHost.php: -------------------------------------------------------------------------------- 1 | $this->instanceId, 62 | 'ip' => $this->ip, 63 | 'port' => $this->port, 64 | 'weight' => $this->weight, 65 | 'healthy' => $this->healthy, 66 | 'enabled' => $this->enabled, 67 | 'ephemeral' => $this->ephemeral, 68 | 'clusterName' => $this->clusterName, 69 | 'serviceName' => $this->serviceName, 70 | 'metadata' => $this->metadata, 71 | 'instanceHeartBeatTimeOut' => $this->instanceHeartBeatTimeOut, 72 | 'instanceHeartBeatInterval' => $this->instanceHeartBeatInterval, 73 | 'instanceIdGenerator' => $this->instanceIdGenerator, 74 | 'ipDeleteTimeout' => $this->ipDeleteTimeout, 75 | ]; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Protobuf/ServiceInfo.php: -------------------------------------------------------------------------------- 1 | $this->name, 61 | 'groupName' => $this->groupName, 62 | 'clusters' => $this->clusters, 63 | 'cacheMillis' => $this->cacheMillis, 64 | 'hosts' => $this->hosts, 65 | 'lastRefTime' => $this->lastRefTime, 66 | 'checksum' => $this->checksum, 67 | 'allIPs' => $this->allIPs, 68 | 'reachProtectionThreshold' => $this->reachProtectionThreshold, 69 | 'valid' => $this->valid, 70 | ]; 71 | } 72 | 73 | public function toKeyString(): string 74 | { 75 | return self::getKeyString($this->clusters, $this->groupName, $this->name); 76 | } 77 | 78 | public static function getKeyString(string $clusters, string $group, string $service): string 79 | { 80 | return sprintf('%s#%s@@%s', $clusters, $group, $service); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Protobuf/any.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "github.com/golang/protobuf/ptypes/any"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "AnyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | option php_namespace = 'Hyperf\\Nacos\\Protobuf'; 42 | option php_metadata_namespace = 'Hyperf\\Nacos\\Protobuf\\GPBMetadata'; 43 | 44 | // `Any` contains an arbitrary serialized protocol buffer message along with a 45 | // URL that describes the type of the serialized message. 46 | // 47 | // Protobuf library provides support to pack/unpack Any values in the form 48 | // of utility functions or additional generated methods of the Any type. 49 | // 50 | // Example 1: Pack and unpack a message in C++. 51 | // 52 | // Foo foo = ...; 53 | // Any any; 54 | // any.PackFrom(foo); 55 | // ... 56 | // if (any.UnpackTo(&foo)) { 57 | // ... 58 | // } 59 | // 60 | // Example 2: Pack and unpack a message in Java. 61 | // 62 | // Foo foo = ...; 63 | // Any any = Any.pack(foo); 64 | // ... 65 | // if (any.is(Foo.class)) { 66 | // foo = any.unpack(Foo.class); 67 | // } 68 | // 69 | // Example 3: Pack and unpack a message in Python. 70 | // 71 | // foo = Foo(...) 72 | // any = Any() 73 | // any.Pack(foo) 74 | // ... 75 | // if any.Is(Foo.DESCRIPTOR): 76 | // any.Unpack(foo) 77 | // ... 78 | // 79 | // Example 4: Pack and unpack a message in Go 80 | // 81 | // foo := &pb.Foo{...} 82 | // any, err := ptypes.MarshalAny(foo) 83 | // ... 84 | // foo := &pb.Foo{} 85 | // if err := ptypes.UnmarshalAny(any, foo); err != nil { 86 | // ... 87 | // } 88 | // 89 | // The pack methods provided by protobuf library will by default use 90 | // 'type.googleapis.com/full.type.name' as the type URL and the unpack 91 | // methods only use the fully qualified type name after the last '/' 92 | // in the type URL, for example "foo.bar.com/x/y.z" will yield type 93 | // name "y.z". 94 | // 95 | // 96 | // JSON 97 | // ==== 98 | // The JSON representation of an `Any` value uses the regular 99 | // representation of the deserialized, embedded message, with an 100 | // additional field `@type` which contains the type URL. Example: 101 | // 102 | // package google.profile; 103 | // message Person { 104 | // string first_name = 1; 105 | // string last_name = 2; 106 | // } 107 | // 108 | // { 109 | // "@type": "type.googleapis.com/google.profile.Person", 110 | // "firstName": , 111 | // "lastName": 112 | // } 113 | // 114 | // If the embedded message type is well-known and has a custom JSON 115 | // representation, that representation will be embedded adding a field 116 | // `value` which holds the custom JSON in addition to the `@type` 117 | // field. Example (for message [google.protobuf.Duration][]): 118 | // 119 | // { 120 | // "@type": "type.googleapis.com/google.protobuf.Duration", 121 | // "value": "1.212s" 122 | // } 123 | // 124 | message Any { 125 | // A URL/resource name that uniquely identifies the type of the serialized 126 | // protocol buffer message. This string must contain at least 127 | // one "/" character. The last segment of the URL's path must represent 128 | // the fully qualified name of the type (as in 129 | // `path/google.protobuf.Duration`). The name should be in a canonical form 130 | // (e.g., leading "." is not accepted). 131 | // 132 | // In practice, teams usually precompile into the binary all types that they 133 | // expect it to use in the context of Any. However, for URLs which use the 134 | // scheme `http`, `https`, or no scheme, one can optionally set up a type 135 | // server that maps type URLs to message definitions as follows: 136 | // 137 | // * If no scheme is provided, `https` is assumed. 138 | // * An HTTP GET on the URL must yield a [google.protobuf.Type][] 139 | // value in binary format, or produce an error. 140 | // * Applications are allowed to cache lookup results based on the 141 | // URL, or have them precompiled into a binary to avoid any 142 | // lookup. Therefore, binary compatibility needs to be preserved 143 | // on changes to types. (Use versioned type names to manage 144 | // breaking changes.) 145 | // 146 | // Note: this functionality is not currently available in the official 147 | // protobuf release, and it is not used for type URLs beginning with 148 | // type.googleapis.com. 149 | // 150 | // Schemes other than `http`, `https` (or the empty scheme) might be 151 | // used with implementation specific semantics. 152 | // 153 | string type_url = 1; 154 | 155 | // Must be a valid serialized protocol buffer of the above specified type. 156 | bytes value = 2; 157 | } 158 | -------------------------------------------------------------------------------- /src/Protobuf/nacos.proto: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | syntax = "proto3"; 19 | 20 | import "any.proto"; 21 | 22 | option java_multiple_files = true; 23 | option java_package = "com.alibaba.nacos.api.grpc.auto"; 24 | option php_namespace = 'Hyperf\\Nacos\\Protobuf'; 25 | option php_metadata_namespace = 'Hyperf\\Nacos\\Protobuf\\GPBMetadata'; 26 | 27 | message Metadata { 28 | string type = 3; 29 | string clientIp = 8; 30 | map headers = 7; 31 | } 32 | 33 | 34 | message Payload { 35 | Metadata metadata = 2; 36 | google.protobuf.Any body = 3; 37 | } 38 | 39 | service RequestStream { 40 | // build a streamRequest 41 | rpc requestStream (Payload) returns (stream Payload) { 42 | } 43 | } 44 | 45 | service Request { 46 | // Sends a commonRequest 47 | rpc request (Payload) returns (Payload) { 48 | } 49 | } 50 | 51 | service BiRequestStream { 52 | // Sends a commonRequest 53 | rpc requestBiStream (stream Payload) returns (stream Payload) { 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Provider/AccessToken.php: -------------------------------------------------------------------------------- 1 | config->getUsername(); 24 | $password = $this->config->getPassword(); 25 | 26 | if ($username === null || $password === null) { 27 | return null; 28 | } 29 | 30 | if (! $this->isExpired()) { 31 | return $this->accessToken; 32 | } 33 | 34 | $result = $this->handleResponse( 35 | $this->app->auth->login($username, $password) 36 | ); 37 | 38 | $this->accessToken = $result['accessToken']; 39 | $this->expireTime = $result['tokenTtl'] + time(); 40 | 41 | return $this->accessToken; 42 | } 43 | 44 | protected function isExpired(): bool 45 | { 46 | if (isset($this->accessToken) && $this->expireTime > time() + 60) { 47 | return false; 48 | } 49 | return true; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Provider/AuthProvider.php: -------------------------------------------------------------------------------- 1 | client()->request('POST', 'nacos/v1/auth/users/login', [ 24 | RequestOptions::QUERY => [ 25 | 'username' => $username, 26 | ], 27 | RequestOptions::FORM_PARAMS => [ 28 | 'password' => $password, 29 | ], 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Provider/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | request('GET', 'nacos/v1/cs/configs', [ 29 | RequestOptions::QUERY => $this->filter([ 30 | 'dataId' => $dataId, 31 | 'group' => $group, 32 | 'tenant' => $tenant, 33 | ]), 34 | ]); 35 | } 36 | 37 | public function set(string $dataId, string $group, string $content, ?string $type = null, ?string $tenant = null): ResponseInterface 38 | { 39 | return $this->request('POST', 'nacos/v1/cs/configs', [ 40 | RequestOptions::FORM_PARAMS => $this->filter([ 41 | 'dataId' => $dataId, 42 | 'group' => $group, 43 | 'tenant' => $tenant, 44 | 'type' => $type, 45 | 'content' => $content, 46 | ]), 47 | ]); 48 | } 49 | 50 | public function delete(string $dataId, string $group, ?string $tenant = null): ResponseInterface 51 | { 52 | return $this->request('DELETE', 'nacos/v1/cs/configs', [ 53 | RequestOptions::QUERY => $this->filter([ 54 | 'dataId' => $dataId, 55 | 'group' => $group, 56 | 'tenant' => $tenant, 57 | ]), 58 | ]); 59 | } 60 | 61 | public function listener( 62 | #[ArrayShape([ 63 | 'dataId' => 'string', 64 | 'group' => 'string', 65 | 'contentMD5' => 'string', // md5(file_get_contents($configPath)) 66 | 'tenant' => 'string', 67 | ])] 68 | array $options = [] 69 | ): ResponseInterface { 70 | $config = ($options['dataId'] ?? null) . self::WORD_SEPARATOR 71 | . ($options['group'] ?? null) . self::WORD_SEPARATOR 72 | . ($options['contentMD5'] ?? null) . self::WORD_SEPARATOR 73 | . ($options['tenant'] ?? null) . self::LINE_SEPARATOR; 74 | return $this->request('POST', 'nacos/v1/cs/configs/listener', [ 75 | RequestOptions::QUERY => [ 76 | 'Listening-Configs' => $config, 77 | ], 78 | RequestOptions::HEADERS => [ 79 | 'Long-Pulling-Timeout' => 30, 80 | ], 81 | ]); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Provider/InstanceProvider.php: -------------------------------------------------------------------------------- 1 | '', 29 | 'clusterName' => '', 30 | 'namespaceId' => '', 31 | 'weight' => 99.0, 32 | 'metadata' => '', 33 | 'enabled' => true, 34 | 'ephemeral' => false, // 是否临时实例 35 | ])] 36 | array $optional = [] 37 | ): ResponseInterface { 38 | return $this->request('POST', 'nacos/v1/ns/instance', [ 39 | RequestOptions::QUERY => $this->filter(array_merge($optional, [ 40 | 'serviceName' => $serviceName, 41 | 'ip' => $ip, 42 | 'port' => $port, 43 | ])), 44 | ]); 45 | } 46 | 47 | public function delete( 48 | string $serviceName, 49 | string $groupName, 50 | string $ip, 51 | int $port, 52 | #[ArrayShape([ 53 | 'clusterName' => '', 54 | 'namespaceId' => '', 55 | 'ephemeral' => false, 56 | ])] 57 | array $optional = [] 58 | ): ResponseInterface { 59 | return $this->request('DELETE', 'nacos/v1/ns/instance', [ 60 | RequestOptions::QUERY => $this->filter(array_merge($optional, [ 61 | 'serviceName' => $serviceName, 62 | 'groupName' => $groupName, 63 | 'ip' => $ip, 64 | 'port' => $port, 65 | ])), 66 | ]); 67 | } 68 | 69 | public function update( 70 | string $ip, 71 | int $port, 72 | string $serviceName, 73 | #[ArrayShape([ 74 | 'groupName' => '', 75 | 'clusterName' => '', 76 | 'namespaceId' => '', 77 | 'weight' => 0.99, 78 | 'metadata' => '', // json 79 | 'enabled' => false, 80 | 'ephemeral' => false, 81 | ])] 82 | array $optional = [] 83 | ): ResponseInterface { 84 | return $this->request('PUT', 'nacos/v1/ns/instance', [ 85 | RequestOptions::QUERY => $this->filter(array_merge($optional, [ 86 | 'serviceName' => $serviceName, 87 | 'ip' => $ip, 88 | 'port' => $port, 89 | ])), 90 | ]); 91 | } 92 | 93 | public function list( 94 | string $serviceName, 95 | #[ArrayShape([ 96 | 'groupName' => '', 97 | 'namespaceId' => '', 98 | 'clusters' => '', // 集群名称(字 99 | 'healthyOnly' => false, 100 | ])] 101 | array $optional = [] 102 | ): ResponseInterface { 103 | return $this->request('GET', 'nacos/v1/ns/instance/list', [ 104 | RequestOptions::QUERY => $this->filter(array_merge($optional, [ 105 | 'serviceName' => $serviceName, 106 | ])), 107 | ]); 108 | } 109 | 110 | public function detail( 111 | string $ip, 112 | int $port, 113 | string $serviceName, 114 | #[ArrayShape([ 115 | 'groupName' => '', 116 | 'namespaceId' => '', 117 | 'cluster' => '', 118 | 'healthyOnly' => false, 119 | 'ephemeral' => false, 120 | ])] 121 | array $optional = [] 122 | ): ResponseInterface { 123 | return $this->request('GET', 'nacos/v1/ns/instance', [ 124 | RequestOptions::QUERY => $this->filter(array_merge($optional, [ 125 | 'ip' => $ip, 126 | 'port' => $port, 127 | 'serviceName' => $serviceName, 128 | ])), 129 | ]); 130 | } 131 | 132 | public function beat( 133 | string $serviceName, 134 | #[ArrayShape([ 135 | 'ip' => '', 136 | 'port' => 9501, 137 | 'serviceName' => '', 138 | 'cluster' => '', 139 | 'weight' => 1, 140 | ])] 141 | array $beat = [], 142 | ?string $groupName = null, 143 | ?string $namespaceId = null, 144 | ?bool $ephemeral = null, 145 | bool $lightBeatEnabled = false 146 | ): ResponseInterface { 147 | return $this->request('PUT', 'nacos/v1/ns/instance/beat', [ 148 | RequestOptions::QUERY => $this->filter([ 149 | 'serviceName' => $serviceName, 150 | 'ip' => $beat['ip'] ?? null, 151 | 'port' => $beat['port'] ?? null, 152 | 'groupName' => $groupName, 153 | 'namespaceId' => $namespaceId, 154 | 'ephemeral' => $ephemeral, 155 | 'beat' => ! $lightBeatEnabled ? Json::encode($beat) : '', 156 | ]), 157 | ]); 158 | } 159 | 160 | public function updateHealth( 161 | string $ip, 162 | int $port, 163 | string $serviceName, 164 | bool $healthy, 165 | #[ArrayShape([ 166 | 'namespaceId' => '', 167 | 'groupName' => '', 168 | 'clusterName' => '', 169 | ])] 170 | array $optional = [] 171 | ): ResponseInterface { 172 | return $this->request('PUT', 'nacos/v1/ns/health/instance', [ 173 | RequestOptions::QUERY => $this->filter(array_merge($optional, [ 174 | 'ip' => $ip, 175 | 'port' => $port, 176 | 'serviceName' => $serviceName, 177 | 'healthy' => $healthy, 178 | ])), 179 | ]); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Provider/OperatorProvider.php: -------------------------------------------------------------------------------- 1 | request('GET', 'nacos/v1/ns/operator/switches'); 24 | } 25 | 26 | public function updateSwitches(string $entry, string $value, ?bool $debug = null): ResponseInterface 27 | { 28 | return $this->request('PUT', 'nacos/v1/ns/operator/switches', [ 29 | RequestOptions::QUERY => $this->filter([ 30 | 'entry' => $entry, 31 | 'value' => $value, 32 | 'debug' => $debug, 33 | ]), 34 | ]); 35 | } 36 | 37 | public function getMetrics(): ResponseInterface 38 | { 39 | return $this->request('GET', 'nacos/v1/ns/operator/metrics'); 40 | } 41 | 42 | public function getServers(?bool $healthy = null): ResponseInterface 43 | { 44 | return $this->request('GET', 'nacos/v1/ns/operator/servers', [ 45 | RequestOptions::QUERY => $this->filter([ 46 | 'healthy' => $healthy, 47 | ]), 48 | ]); 49 | } 50 | 51 | public function getLeader(): ResponseInterface 52 | { 53 | return $this->request('GET', 'nacos/v1/ns/raft/leader'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Provider/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | '', 26 | 'namespaceId' => '', 27 | 'protectThreshold' => 0.99, 28 | 'metadata' => '', 29 | 'selector' => '', // json字符串 30 | ])] 31 | array $optional = [] 32 | ): ResponseInterface { 33 | return $this->request('POST', 'nacos/v1/ns/service', [ 34 | RequestOptions::QUERY => $this->filter(array_merge($optional, [ 35 | 'serviceName' => $serviceName, 36 | ])), 37 | ]); 38 | } 39 | 40 | public function delete(string $serviceName, ?string $groupName = null, ?string $namespaceId = null): ResponseInterface 41 | { 42 | return $this->request('DELETE', 'nacos/v1/ns/service', [ 43 | RequestOptions::QUERY => $this->filter([ 44 | 'serviceName' => $serviceName, 45 | 'groupName' => $groupName, 46 | 'namespaceId' => $namespaceId, 47 | ]), 48 | ]); 49 | } 50 | 51 | public function update( 52 | string $serviceName, 53 | #[ArrayShape([ 54 | 'groupName' => '', 55 | 'namespaceId' => '', 56 | 'protectThreshold' => 0.99, 57 | 'metadata' => '', 58 | 'selector' => '', // json字符串 59 | ])] 60 | array $optional = [] 61 | ): ResponseInterface { 62 | return $this->request('PUT', 'nacos/v1/ns/service', [ 63 | RequestOptions::QUERY => $this->filter(array_merge($optional, [ 64 | 'serviceName' => $serviceName, 65 | ])), 66 | ]); 67 | } 68 | 69 | public function detail(string $serviceName, ?string $groupName = null, ?string $namespaceId = null): ResponseInterface 70 | { 71 | return $this->request('GET', 'nacos/v1/ns/service', [ 72 | RequestOptions::QUERY => $this->filter([ 73 | 'serviceName' => $serviceName, 74 | 'groupName' => $groupName, 75 | 'namespaceId' => $namespaceId, 76 | ]), 77 | ]); 78 | } 79 | 80 | public function list(int $pageNo, int $pageSize, ?string $groupName = null, ?string $namespaceId = null): ResponseInterface 81 | { 82 | return $this->request('GET', 'nacos/v1/ns/service/list', [ 83 | RequestOptions::QUERY => $this->filter([ 84 | 'pageNo' => $pageNo, 85 | 'pageSize' => $pageSize, 86 | 'groupName' => $groupName, 87 | 'namespaceId' => $namespaceId, 88 | ]), 89 | ]); 90 | } 91 | } 92 | --------------------------------------------------------------------------------