From 9b82b756b482d9553e533418b901a8d45a184504 Mon Sep 17 00:00:00 2001 From: greezen Date: Wed, 15 Feb 2023 09:35:49 +0800 Subject: [PATCH] feat: add confd nacos driver (#129) * feat: add confd nacos driver --------- Co-authored-by: greezen Co-authored-by: Deeka Wong <8337659+huangdijia@users.noreply.github.com> --- composer.json | 3 +- src/confd/README.md | 6 +- src/confd/composer.json | 5 +- src/confd/publish/confd.php | 32 ++++++++ src/confd/src/Driver/Etcd.php | 2 +- src/confd/src/Driver/Nacos.php | 143 +++++++++++++++++++++++++++++++++ 6 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 src/confd/src/Driver/Nacos.php diff --git a/composer.json b/composer.json index 5950ced83..9e079a1b9 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,6 @@ "hyperf/db-connection": "~3.0.0", "hyperf/di": "~3.0.0", "hyperf/engine": "^1.3|^2.0", - "hyperf/etcd": "~3.0.0", "hyperf/filesystem": "~3.0.0", "hyperf/framework": "~3.0.0", "hyperf/grpc-server": "~3.0.0", @@ -57,7 +56,9 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.0", + "hyperf/etcd": "~3.0.0", "hyperf/ide-helper": "~3.0.0", + "hyperf/nacos": "~3.0.0", "mockery/mockery": "^1.0", "phpstan/phpstan": "^1.0", "phpunit/phpunit": "^9.5", diff --git a/src/confd/README.md b/src/confd/README.md index 51fee158f..6c8495f7e 100644 --- a/src/confd/README.md +++ b/src/confd/README.md @@ -15,11 +15,14 @@ The confd component for Hyperf. ```shell composer require friendsofhyperf/confd +composer require friendsofhyperf/etcd +# or +composer require friendsofhyperf/nacos ``` ## Command -Fetch configs from etcd/consul and upgrade `.env`. +Fetch configs from etcd/nacos and upgrade `.env`. ```shell php bin/hyperf.php confd:env @@ -62,6 +65,7 @@ class ConfigChangedListener implements ListenerInterface ## Support - [x] Etcd +- [x] Nacos - [ ] Consul ## Sponsor diff --git a/src/confd/composer.json b/src/confd/composer.json index bcad25f02..0d9360b24 100644 --- a/src/confd/composer.json +++ b/src/confd/composer.json @@ -11,7 +11,6 @@ "hyperf/command": "~3.0.0", "hyperf/config-center": "~3.0.0", "hyperf/di": "~3.0.0", - "hyperf/etcd": "~3.0.0", "hyperf/event": "~3.0.0", "hyperf/framework": "~3.0.0", "hyperf/utils": "~3.0.0" @@ -29,5 +28,9 @@ "hyperf": { "config": "FriendsOfHyperf\\Confd\\ConfigProvider" } + }, + "suggest": { + "hyperf/nacos": "For nacos driver.", + "hyperf/etcd": "For etcd driver." } } \ No newline at end of file diff --git a/src/confd/publish/confd.php b/src/confd/publish/confd.php index fc766ab79..d4cac4f6a 100644 --- a/src/confd/publish/confd.php +++ b/src/confd/publish/confd.php @@ -29,6 +29,38 @@ '/test/foo', ], ], + 'nacos' => [ + 'driver' => \FriendsOfHyperf\Confd\Driver\Nacos::class, + 'client' => [ + 'host' => '127.0.0.1', + 'port' => 8848, + 'username' => 'nacos', + 'password' => 'nacos', + 'guzzle' => [ + 'config' => ['timeout' => 3, 'connect_timeout' => 1], + ], + ], + 'listener_config' => [ + 'mysql' => [ + 'tenant' => 'framework', + 'data_id' => 'mysql', + 'group' => 'DEFAULT_GROUP', + 'type' => 'json', + ], + ], + 'mapping' => [ + 'mysql' => ['charset' => 'DB_CHARSET'], + 'redis' => ['port' => 'REDIS_PORT'], + ], + 'watches' => [ + 'test' => [ + 'tenant' => 'framework', + 'data_id' => 'test', + 'group' => 'DEFAULT_GROUP', + 'type' => 'text', + ], + ], + ], ], 'env_path' => BASE_PATH . '/.env', diff --git a/src/confd/src/Driver/Etcd.php b/src/confd/src/Driver/Etcd.php index a9c35ad77..1fc2326ae 100644 --- a/src/confd/src/Driver/Etcd.php +++ b/src/confd/src/Driver/Etcd.php @@ -53,7 +53,7 @@ public function getChanges(): array ->filter(fn ($kv) => in_array($kv['key'], $watches)) ->mapWithKeys(fn ($kv) => [$kv['key'] => $kv['value']]) ->toArray(); - $changes = array_diff($values, $this->origins); + $changes = array_diff_assoc($values, $this->origins); if (! $this->origins) { // Return [] when first run. return tap([], fn () => $this->origins = $values); diff --git a/src/confd/src/Driver/Nacos.php b/src/confd/src/Driver/Nacos.php new file mode 100644 index 000000000..daa0c4ff3 --- /dev/null +++ b/src/confd/src/Driver/Nacos.php @@ -0,0 +1,143 @@ +client = make(Application::class, [ + 'config' => $this->pendingNacosConfig(), + ]); + } + + /** + * get all listener config from nacos server. + */ + public function fetch(): array + { + $listener = $this->config->get('confd.drivers.nacos.listener_config', []); + $mapping = (array) $this->config->get('confd.drivers.nacos.mapping', []); + + $config = []; + foreach ($listener as $key => $item) { + $config = collect($this->pullConfig($item)) + ->filter(fn ($item, $k) => isset($mapping[$key][$k])) + ->mapWithKeys(fn ($item, $k) => [$mapping[$key][$k] => is_array($item) ? implode(',', $item) : $item]) + ->merge($config) + ->toArray(); + } + return $config; + } + + /** + * check watch config is chaged. + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Hyperf\Utils\Exception\InvalidArgumentException + */ + public function getChanges(): array + { + $watches = (array) $this->config->get('confd.drivers.nacos.watches', []); + + $config = []; + foreach ($watches as $item) { + $configKey = sprintf('%s.%s.%s', $item['group'], $item['tenant'] ?? 'default', $item['data_id']); + $config = collect($this->pullConfig($item)) + ->mapWithKeys(fn ($value) => [$configKey => is_array($value) ? implode(',', $value) : $value]) + ->merge($config) + ->toArray(); + } + + if (! $this->origins) { // Return [] when first run. + return tap([], fn () => $this->origins = $config); + } + + $changes = array_diff_assoc($config, $this->origins); + + return tap($changes, function ($changes) use ($config) { + if ($changes) { + $this->logger->debug('[confd#nacos] Config changed.'); + } + + $this->origins = $config; + }); + } + + protected function decode(string $body, ?string $type = null): mixed + { + $type = strtolower((string) $type); + switch ($type) { + case 'json': + return Json::decode($body); + case 'yml': + case 'yaml': + return yaml_parse($body); + case 'xml': + return Xml::toArray($body); + default: + return $body; + } + } + + /** + * get nacos client config from confd setting. + */ + protected function pendingNacosConfig(): Config + { + $clientConfig = $this->config->get('confd.drivers.nacos.client', []); + + if (! empty($clientConfig['uri'])) { + $baseUri = $clientConfig['uri']; + } else { + $baseUri = sprintf('http://%s:%d', $clientConfig['host'] ?? '127.0.0.1', $clientConfig['port'] ?? 8848); + } + + return new Config([ + 'base_uri' => $baseUri, + 'username' => $clientConfig['username'] ?? null, + 'password' => $clientConfig['password'] ?? null, + 'guzzle_config' => $clientConfig['guzzle']['config'] ?? null, + ]); + } + + /** + * pull fresh config from nacos server. + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Hyperf\Utils\Exception\InvalidArgumentException + */ + protected function pullConfig(array $listenerConfig): array|string + { + $dataId = $listenerConfig['data_id']; + $group = $listenerConfig['group']; + $tenant = $listenerConfig['tenant'] ?? null; + $type = $listenerConfig['type'] ?? null; + $response = $this->client->config->get($dataId, $group, $tenant); + + if ($response->getStatusCode() !== 200) { + $this->logger->error(sprintf('The config of %s.%s.%s read failed from Nacos.', $group, $tenant, $dataId)); + return []; + } + + return $this->decode((string) $response->getBody(), $type); + } +}