Added foundation of apollo config-center

This commit is contained in:
huangzhhui 2019-02-26 00:57:26 +08:00
parent 608a18ef46
commit 3b47191a62
8 changed files with 468 additions and 1 deletions

View File

@ -46,7 +46,9 @@
"hyperf/amqp": "self.version",
"hyperf/cache": "self.version",
"hyperf/config": "self.version",
"hyperf/config-apollo": "self.version",
"hyperf/constants": "self.version",
"hyperf/consul": "self.version",
"hyperf/contract": "self.version",
"hyperf/database": "self.version",
"hyperf/db-connection": "self.version",
@ -80,7 +82,9 @@
"Hyperf\\Amqp\\": "src/amqp/src/",
"Hyperf\\Cache\\": "src/cache/src/",
"Hyperf\\Config\\": "src/config/src/",
"Hyperf\\ConfigApollo\\": "src/config-apollo/src/",
"Hyperf\\Constants\\": "src/constants/src/",
"Hyperf\\Consul\\": "src/consul/src/",
"Hyperf\\Contract\\": "src/contract/src/",
"Hyperf\\Database\\": "src/database/src/",
"Hyperf\\DbConnection\\": "src/db-connection/src/",
@ -106,9 +110,12 @@
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\ConfigApollo\\": "./src/config-apollo/tests",
"HyperfTest\\Consul\\": "./src/consul/tests",
"HyperfTest\\Dispatcher\\": "./src/dispatcher/test",
"HyperfTest\\Database\\": "./src/database/tests",
"HyperfTest\\Event\\": "./src/event/tests"
"HyperfTest\\Event\\": "./src/event/tests",
"HyperfTest\\Utils\\": "./src/utils/tests"
}
},
"config": {
@ -122,6 +129,7 @@
"Hyperf\\Amqp\\ConfigProvider",
"Hyperf\\Cache\\ConfigProvider",
"Hyperf\\Config\\ConfigProvider",
"Hyperf\\ConfigApollo\\ConfigProvider",
"Hyperf\\Devtool\\ConfigProvider",
"Hyperf\\DbConnection\\ConfigProvider",
"Hyperf\\Di\\ConfigProvider",

View File

@ -0,0 +1,55 @@
{
"name": "hyperf/config-apollo",
"description": "An apollo adapter for Hyperf config component.",
"license": "Apache-2.0",
"keywords": [
"php",
"swoole",
"hyperf",
"config",
"configuration",
"apollo"
],
"support": {
},
"require": {
"php": ">=7.2",
"hyperf/config": "dev-master"
},
"require-dev": {
"malukenho/docheader": "^0.1.6",
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^7.0.0",
"friendsofphp/php-cs-fixer": "^2.9"
},
"suggest": {
},
"autoload": {
"files": [
],
"psr-4": {
"Hyperf\\ConfigApollo\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\ConfigApollo\\": "tests/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
},
"hyperf": {
"config": "Hyperf\\ConfigApollo\\ConfigProvider"
}
},
"bin": [
],
"scripts": {
"cs-fix": "php-cs-fixer fix $1",
"test": "phpunit --colors=always"
}
}

View File

@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\ConfigApollo;
use Closure;
use Hyperf\Utils\Parallel;
use Hyperf\Utils\Coroutine;
class Client
{
/**
* @var Option
*/
private $option;
/**
* @var array
*/
private $callbacks;
/**
* @var Closure
*/
private $httpClientFactory;
public function __construct(
Option $option,
array $callbacks = [],
Closure $httpClientFactory
) {
$this->option = $option;
$this->callbacks = $callbacks;
$this->httpClientFactory = $httpClientFactory;
}
public function pull(array $namespaces)
{
if (! $namespaces) {
return [];
}
if (Coroutine::inCoroutine()) {
// @todo needs test.
$result = $this->coroutinePull($namespaces);
} else {
$result = $this->blockingPull($namespaces);
}
foreach ($result as $namespace => $value) {
if (isset($this->callbacks[$namespace]) && is_callable($this->callbacks[$namespace])) {
call($this->callbacks[$namespace], [$value]);
if (isset($value['releaseKey']) && $value['releaseKey']) {
ReleaseKey::set($namespace, $value['releaseKey']);
}
}
}
}
protected function coroutinePull(array $namespaces)
{
$option = $this->option;
$parallel = new Parallel();
$httpClientFactory = $this->httpClientFactory;
foreach ($namespaces as $namespace) {
$parallel->add(function () use ($httpClientFactory, $option, $namespace) {
$client = $httpClientFactory();
if (! $client instanceof \GuzzleHttp\Client) {
throw new \RuntimeException('Invalid http client.');
}
$releaseKey = ReleaseKey::get($namespace, null);
$response = $client->get($option->buildBaseUrl() . $namespace, [
'query' => [
'ip' => $option->getClientIp(),
'releaseKey' => $releaseKey,
],
]);
if ($response->getStatusCode() === 200 && strpos($response->getHeaderLine('Content-Type'), 'application/json') !== false) {
$body = json_decode((string)$response->getBody(), true);
$result[$namespace] = [
'configurations' => $body['configurations'] ?? [],
'releaseKey' => $body['releaseKey'] ?? '',
];
} else {
$result[$namespace] = [
'configurations' => [],
'releaseKey' => '',
];
}
});
}
return $parallel->wait();
}
protected function blockingPull(array $namespaces)
{
$result = [];
$url = $this->option->buildBaseUrl();
$httpClientFactory = $this->httpClientFactory;
foreach ($namespaces as $namespace) {
$client = $httpClientFactory();
if (! $client instanceof \GuzzleHttp\Client) {
throw new \RuntimeException('Invalid http client.');
}
$releaseKey = ReleaseKey::get($namespace, null);
$response = $client->get($url . $namespace, [
'query' => [
'ip' => $this->option->getClientIp(),
'releaseKey' => $releaseKey,
],
]);
if ($response->getStatusCode() === 200 && strpos($response->getHeaderLine('Content-Type'), 'application/json') !== false) {
$body = json_decode((string)$response->getBody(), true);
$result[$namespace] = [
'configurations' => $body['configurations'] ?? [],
'releaseKey' => $body['releaseKey'] ?? '',
];
} else {
$result[$namespace] = [
'configurations' => [],
'releaseKey' => '',
];
}
}
return $result;
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\ConfigApollo;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
],
'scan' => [
'paths' => [
__DIR__,
],
],
];
}
}

View File

@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\ConfigApollo;
use Hyperf\Utils\Str;
class Option
{
/**
* @var string
*/
private $server = '';
/**
* @var string
*/
private $appid = '';
/**
* @var array
*/
private $namespaces = [];
/**
* @var string
*/
private $cluster = 'default';
/**
* @var string
*/
private $clientIp = '127.0.0.1';
/**
* @var int
*/
private $pullTimeout = 10;
/**
* @var int
*/
private $intervalTimeout = 60;
public function buildBaseUrl(): string
{
return implode('/', [
$this->getServer(),
'configs',
$this->getAppid(),
$this->getCluster()
]) . '/';
}
public function getServer(): string
{
return $this->server;
}
public function setServer(string $server): self
{
if (! Str::startsWith($server, 'http://')) {
$server = 'http://' . $server;
}
$this->server = $server;
return $this;
}
public function getAppid(): string
{
return $this->appid;
}
public function setAppid(string $appid): self
{
$this->appid = $appid;
return $this;
}
public function getNamespaces(): array
{
return $this->namespaces;
}
public function setNamespaces(string $namespaces): self
{
$this->namespaces = $namespaces;
return $this;
}
public function getCluster(): string
{
return $this->cluster;
}
public function setCluster(string $cluster): self
{
$this->cluster = $cluster;
return $this;
}
public function getClientIp(): string
{
return $this->clientIp;
}
public function setClientIp(string $clientIp): self
{
$this->clientIp = $clientIp;
return $this;
}
public function getPullTimeout(): int
{
return $this->pullTimeout;
}
public function setPullTimeout(int $pullTimeout): self
{
$this->pullTimeout = $pullTimeout;
return $this;
}
public function getIntervalTimeout(): int
{
return $this->intervalTimeout;
}
public function setIntervalTimeout(int $intervalTimeout): self
{
$this->intervalTimeout = $intervalTimeout;
return $this;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Hyperf\ConfigApollo;
use Hyperf\Utils\Traits\Container;
class ReleaseKey
{
use Container;
/**
* @var array
*/
protected static $container = [];
}

View File

@ -0,0 +1,46 @@
<?php
namespace HyperfTest\ConfigApollo;
use Hyperf\Config\Config;
use Hyperf\ConfigApollo\Client;
use Hyperf\ConfigApollo\Option;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Guzzle\ClientFactory;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Coroutine;
use Mockery;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
class ClientTest extends TestCase
{
public function testPull()
{
$option = new Option();
$option->setServer('http://127.0.0.1:8080')->setAppid('test')->setCluster('default')->setClientIp('127.0.0.1');
$container = Mockery::mock(ContainerInterface::class);
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn(new Config([]));
ApplicationContext::setContainer($container);
$callbacks = [
'application' => function ($configs) {
$container = ApplicationContext::getContainer();
$config = $container->get(ConfigInterface::class);
foreach ($configs['configurations'] ?? [] as $key => $value) {
$config->set($key, $value);
}
},
];
$client = new Client($option, $callbacks, function (array $options = []) use ($container) {
return (new ClientFactory($container))->create($options);
});
$client->pull([
'application',
]);
$config = $container->get(ConfigInterface::class);
$this->assertSame('test-value', $config->get('test-key'));
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\ConfigApollo;
use Hyperf\ConfigApollo\Option;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @covers \Hyperf\ConfigApollo\Option
*/
class OptionTest extends TestCase
{
public function testBuildUrl()
{
$option = new Option();
$option->setServer('http://127.0.0.1:8080')->setAppid('test')->setCluster('default')->setClientIp('127.0.0.1');
$baseUrl = 'http://127.0.0.1:8080/configs/test/default/';
$this->assertSame($baseUrl, $option->buildBaseUrl());
// Server without 'http://'
$option->setServer('127.0.0.1:8080');
$this->assertSame($baseUrl, $option->buildBaseUrl());
}
}