123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- <?php
- class ApolloClient
- {
- protected $configServer; //apollo服务端地址
- protected $appId; //apollo配置项目的appid
- protected $cluster = 'default';
- protected $clientIp = '127.0.0.1'; //绑定IP做灰度发布用
- protected $notifications = array();
- protected $pullTimeout = 10; //获取某个namespace配置的请求超时时间
- protected $intervalTimeout = 10; //每次请求获取apollo配置变更时的超时时间
- public $save_dir; //配置保存目录
- public $publicConfigDir; //公用部分配置路径
- private $checkNotificationsId=array(); //用于读取本地配置版本号,和线上对比(直接使用$notifications,发现读取接口超慢,所以另存一个变量)
- public $replaceValueArr=array();
- /**
- * ApolloClient constructor.
- * @param string $configServer apollo服务端地址
- * @param string $appId apollo配置项目的appid
- * @param array $namespaces apollo配置项目的namespace
- * @param string $cluster 集群名,和项目的testing,dev等环境变量相同
- * @param string $configFile 生成的配置文件地址
- * @param string $config 本地公共的配置文件地址(不需要放到配置中心的本地配置项,一般为不需要变更的配置)
- */
- public function __construct($configServer, $appId, array $namespaces,$cluster,$configFile,$config='')
- {
- $this->configServer = $configServer;
- $this->appId = $appId;
- $this->cluster=$cluster;
- $configFiles=array();
- foreach ($namespaces as $namespace) {
- $this->notifications[$namespace] = array('namespaceName' => $namespace, 'notificationId' => -1);
- $configFiles[$namespace]=$this->getConfigFileNameSpace($cluster,$namespace);
- }
- $this->save_dir = $configFile;
- $this->publicConfigDir=$config;
- //读取配置的版本号
- if($configFiles){
- foreach ($configFiles as $namespace=>$file){
- $filePath= $this->publicConfigDir.$this->getConfigFileNameSpace($cluster,$namespace,1);
- if(file_exists($filePath)){
- $configValues=require $filePath;
- if(isset($configValues['params']['notification'][$namespace])){
- $this->checkNotificationsId[$namespace] = array('namespaceName' => $namespace, 'notificationId' => $configValues['params']['notification'][$namespace]);
- }
- unset($configValues);
- }
- }
- }
- }
- public function setCluster($cluster)
- {
- $this->cluster = $cluster;
- }
- public function setClientIp($ip)
- {
- $this->clientIp = $ip;
- }
- public function setPullTimeout($pullTimeout) {
- $pullTimeout = intval($pullTimeout);
- if ($pullTimeout < 1 || $pullTimeout > 300) {
- return;
- }
- $this->pullTimeout = $pullTimeout;
- }
- public function setIntervalTimeout($intervalTimeout) {
- $intervalTimeout = intval($intervalTimeout);
- if ($intervalTimeout < 1 || $intervalTimeout > 300) {
- return;
- }
- $this->intervalTimeout = $intervalTimeout;
- }
- private function _getReleaseKey($config_file) {
- $releaseKey = '';
- if (file_exists($config_file)) {
- $last_config = require $config_file;
- is_array($last_config) && isset($last_config['releaseKey']) && $releaseKey = $last_config['releaseKey'];
- }
- return $releaseKey;
- }
- //获取单个namespace的配置文件路径
- public function getConfigFile($cluster) {
- return $this->save_dir.DIRECTORY_SEPARATOR.'main.'.$cluster.'.php';
- }
- //获取单个namespace/集群文件名
- public function getConfigFileNameSpace($cluster,$nameSpace,$public=0){
- $fileFirstName='';
- $nameSpace=substr($nameSpace, 0,strrpos($nameSpace, '.'));
- if($nameSpace=='application'){
- $fileFirstName='main';
- }else{
- $fileFirstName=$nameSpace;
- }
- if($cluster=='default'){
- $cluster='production';
- }
- if(!$public){
- return $this->save_dir.DIRECTORY_SEPARATOR.$fileFirstName.'.'.$cluster.'.php';
- }else{
- return $fileFirstName.'.'.$cluster.'.php';
- }
- }
- //获取单个namespace的配置-无缓存的方式
- public function pullConfig($namespaceName) {
- $base_api = rtrim($this->configServer, '/').'/configs/'.$this->appId.'/'.$this->cluster.'/';
- $api = $base_api.$namespaceName;
- $args = array();
- $args['ip'] = $this->clientIp;
- $config_file = $this->getConfigFile($this->cluster);
- $args['releaseKey'] = $this->_getReleaseKey($config_file);
- $api .= '?' . http_build_query($args);
- $ch = curl_init($api);
- curl_setopt($ch, CURLOPT_TIMEOUT, $this->pullTimeout);
- curl_setopt($ch, CURLOPT_HEADER, false);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- $body = curl_exec($ch);
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $error = curl_error($ch);
- curl_close($ch);
- if ($httpCode == 200) {
- $result = json_decode($body, true);
- $content = '<?php return ' . var_export($result, true) . ';';
- file_put_contents($config_file, $content);
- }elseif ($httpCode != 304) {
- echo $body ?: $error."\n";
- return false;
- }
- return true;
- }
- //获取多个namespace的配置-无缓存的方式
- public function pullConfigBatch(array $namespaceNames) {
- if (! $namespaceNames) return array();
- $multi_ch = curl_multi_init();
- $request_list = array();
- $base_url = rtrim($this->configServer, '/').'/configs/'.$this->appId.'/'.$this->cluster.'/';
- $query_args = array();
- $query_args['ip'] = $this->clientIp;
- foreach ($namespaceNames as $namespaceName=>$notificationId) {
- $request = array();
- $request_url = $base_url.$namespaceName;
- // $query_args['releaseKey'] = $this->_getReleaseKey($config_file);
- // print_r($query_args['releaseKey']);exit;
- $query_string = '?'.http_build_query($query_args);
- $request_url .= $query_string;
- $ch = curl_init($request_url);
- curl_setopt($ch, CURLOPT_TIMEOUT, $this->pullTimeout);
- curl_setopt($ch, CURLOPT_HEADER, false);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- $request['ch'] = $ch;
- $request['notificationId'] = $notificationId;
- $request_list[$namespaceName] = $request;
- curl_multi_add_handle($multi_ch, $ch);
- }
- $active = null;
- // 执行批处理句柄
- do {
- $mrc = curl_multi_exec($multi_ch, $active);
- } while ($mrc == CURLM_CALL_MULTI_PERFORM);
- while ($active && $mrc == CURLM_OK) {
- if (curl_multi_select($multi_ch) == -1) {
- usleep(100);
- }
- do {
- $mrc = curl_multi_exec($multi_ch, $active);
- } while ($mrc == CURLM_CALL_MULTI_PERFORM);
-
- }
- // 获取结果
- $response_list = array();
- foreach ($request_list as $namespaceName => $req) {
- $response_list[$namespaceName] = true;
- $result = curl_multi_getcontent($req['ch']);
- $code = curl_getinfo($req['ch'], CURLINFO_HTTP_CODE);
- $error = curl_error($req['ch']);
- curl_multi_remove_handle($multi_ch,$req['ch']);
- curl_close($req['ch']);
- if ($code == 200) {
- $result = json_decode($result,true);
- $result=$this->YamalParseConfigData($result,$namespaceName,$req);
- $configFile=$this->getConfigFileNameSpace($this->cluster,$namespaceName);
- file_put_contents($configFile, $result);
- }elseif ($code != 304) {
- echo 'pull config of namespace['.$namespaceName.'] error:'.($result ?: $error)."\n";
- $response_list[$namespaceName] = false;
- }
- }
- curl_multi_close($multi_ch);
- return $response_list;
- }
- //格式化处理配置参数,合并公用配置
- protected function formatConfigData($result,$namespaceName){
- if(!$result || !is_array($result)) return null;
- $config=array();
- $publicConfig= require $this->publicConfigDir.$this->getConfigFileNameSpace($this->cluster,$namespaceName,1);
- foreach ($result['configurations'] as $key => $value){
- if(strpos($key,'.')!==false){
- $keyArr=explode('.',$key);
- if($keyArr){
- $realVal=json_decode($value,true)?json_decode($value,true):$value;
- $keyStr='$config';
- for($i=0;$i<count($keyArr);$i++){
- $keyStr.="['".$keyArr[$i]."']";
- }
- $keyStr.='=$realVal;';
- eval($keyStr);
- }
- }else{
- $realVal=json_decode($value,true)?json_decode($value,true):$value;
- $config[$key]=$realVal;
- }
- }
- $config=array_merge_recursive($publicConfig,$config);
- return $config;
- }
- //格式化处理配置参数,合并公用配置
- protected function YamalParseConfigData($result,$namespaceName,$req){
- if(!$result || !is_array($result)) return null;
- $content='';
- if(isset($result['configurations']) && isset($result['configurations']['content']) && $result['configurations']['content']){
- $config=yaml_parse($result['configurations']['content']);
- $replaceArr=$this->recur($config);
- if(strpos($namespaceName,'qcloud')!==false && isset($config['string'])){
- $content=" <?php \n ".implode("\n",$config['string'])." ";
- }else{
- $config['params']['notification'][$namespaceName]=$req['notificationId'];
- $content=" <?php return ".var_export($config,true)." ;";
- if($replaceArr){
- $content=str_replace(array_keys($replaceArr),array_values($replaceArr),$content);
- }
- }
- }
- return $content;
- }
- protected function _listenChange(&$ch) {
- $base_url = rtrim($this->configServer, '/').'/notifications/v2?';
- $params = array();
- $params['appId'] = $this->appId;
- $params['cluster'] = $this->cluster;
- $params['notifications'] = json_encode(array_values($this->notifications));
- $query = http_build_query($params);
- curl_setopt($ch, CURLOPT_URL, $base_url.$query);
- $response = curl_exec($ch);
- $httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
- $error = curl_error($ch);
- if ($httpCode == 200) {
- $res = json_decode($response, true);
- $change_list=array();
- foreach ($res as $r) {
- if (!isset($this->checkNotificationsId[$r['namespaceName']]) || $r['notificationId'] != $this->checkNotificationsId[$r['namespaceName']]['notificationId']) {
- $change_list[$r['namespaceName']] = $r['notificationId'];
- }
- }
- if($change_list){
- $response_list= $this->pullConfigBatch($change_list);
- foreach ($response_list as $name => $value){
- if(!$value){
- echo $name.':同步配置失败'.PHP_EOL;
- }else{
- echo $name.':同步配置成功'.PHP_EOL;
- }
- }
- }
- }elseif ($httpCode != 304) {
- throw new \Exception($response ?: $error);
- }
- }
- /**
- * @param $callback 监听到配置变更时的回调处理
- * @return mixed
- */
- public function start() {
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_TIMEOUT, $this->intervalTimeout);
- curl_setopt($ch, CURLOPT_HEADER, false);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- try {
- $this->_listenChange($ch);
- }catch (\Exception $e) {
- curl_close($ch);
- return $e->getMessage();
- }
- }
- public function setReplaceArr($arr){
- $this->replaceValueArr=$arr;
- }
- //查找需要替换的变量
- function recur($array){
- $data = array();
- array_walk_recursive($array, function ($v, $k) use (&$data) {
- if(strpos($v,'////')!==false){
- $data["'".$v."'"]=str_replace('////','',$v);
- }
- if(strpos($k,'////')!==false){
- $data["'".$k."'"]=str_replace('////','',$k);
- }
- });
- return $data;
- }
- }
|