![JAR search and dependency download from the Maven repository](/logo.png)
com.github.javaclub.configcenter.client.DefaultConfigCenterClient Maven / Gradle / Ivy
The newest version!
package com.github.javaclub.configcenter.client;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.javaclub.configcenter.ConfigServerConstants.BootConfig;
import com.github.javaclub.configcenter.ConfigServerConstants.Client;
import com.github.javaclub.configcenter.client.conf.AppCfg;
import com.github.javaclub.configcenter.client.conf.ConfigRemoveFlagHolder;
import com.github.javaclub.configcenter.client.conf.LocalConfigserverConf;
import com.github.javaclub.configcenter.client.domain.CfgVO;
import com.github.javaclub.configcenter.client.domain.ConfigChangedEvent;
import com.github.javaclub.configcenter.client.domain.ServerNode;
import com.github.javaclub.configcenter.client.domain.ServerNodeHolder;
import com.github.javaclub.configcenter.client.util.DESCoder;
import com.github.javaclub.configcenter.client.util.DingTalkUtils;
import com.github.javaclub.configcenter.client.util.HttpHelper;
import com.github.javaclub.configcenter.client.util.PropUtil;
import com.github.javaclub.configcenter.client.util.PropUtil.Configuration;
import com.github.javaclub.configcenter.client.util.Utils;
import com.github.javaclub.toolbox.AppBootHook;
import com.github.javaclub.toolbox.ToolBox.ClassPath;
import com.github.javaclub.toolbox.ToolBox.Numbers;
import com.github.javaclub.toolbox.ToolBox.Objects;
import com.github.javaclub.toolbox.ToolBox.Strings;
import com.github.javaclub.toolbox.ToolBox.Systems;
import com.github.javaclub.toolbox.conf.CompositeAppConfigProperties;
import com.github.javaclub.toolbox.conf.ConfigcenterLoader;
import com.github.javaclub.toolbox.thread.ExecutorServiceInstance;
import com.github.javaclub.toolbox.utils.ThreadLocalDateFormatter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
public class DefaultConfigCenterClient implements ConfigCenterClient {
private static final Logger logger = LoggerFactory.getLogger(DefaultConfigCenterClient.class);
private static volatile String endpoint; // ip:port 如:127.0.0.1:7001
private static volatile ConcurrentMap appMap = new ConcurrentHashMap();
private static volatile ConcurrentMap> cfg = new ConcurrentHashMap>();
private static volatile ExecutorService exector = Executors.newFixedThreadPool(5);
private static volatile AtomicBoolean inited = new AtomicBoolean(false);
private static volatile AtomicInteger pollingFailedCounter = new AtomicInteger(0);
private static volatile int ALARM_BY_MAX_POLLING_FAILED_TIMES = 5; // 1分钟内连续集中式的有5次以上请求错误,会触发报警
private static volatile String env;
private static DefaultConfigCenterClient instance;
public static DefaultConfigCenterClient getInstance() {
if (null == instance) {
synchronized (DefaultConfigCenterClient.class) {
if (null == instance) {
instance = new DefaultConfigCenterClient();
}
}
}
return instance;
}
private DefaultConfigCenterClient() {
String serverIps = this.ensureReady();
Systems.out("Configserver address config: {}", serverIps);
String[] ipArray = Utils.splitAndTrim(serverIps, BootConfig.CONFIGSERVER_ADDRESS_SEPCHAR);
int thisAppId = CompositeAppConfigProperties.getInstance().intValue(BootConfig.CONFIGSERVER_APPID, 0);
for (String ipAndPort : ipArray) {
if (Utils.isBlank(ipAndPort)) {
continue;
}
String testUrl = Utils.concat("http://", ipAndPort.trim(),
"/configcenter/webStatus?startup=true×tamp=", System.currentTimeMillis(),
"&clientVer=", getClientVersion(), "&env=", getEnv(),
"&appName=", CompositeAppConfigProperties.getInstance().getAppName()
);
boolean up = false;
try {
String resp = HttpHelper.get(testUrl);
up = Utils.equals("success", resp);
} catch (Exception e) {
if (thisAppId > 0) {
logger.error("Test Configcenter serverAddress: {}, error: {}", ipAndPort, e.getMessage());
}
}
if (up) {
String svrIpAndPort = ipAndPort.trim();
ServerNodeHolder.getInstance().registerNode(new ServerNode(svrIpAndPort));
if (Utils.isBlank(getEndpoint())) {
setEndpoint(svrIpAndPort);
ConfigServer.getInstance().setEndPoint(svrIpAndPort);
Systems.out("Configserver default endpoint address seleced: {}", svrIpAndPort);
}
}
}
if (thisAppId > 0) {
if (Utils.isBlank(getEndpoint())) {
// throw new ConfigException("No available configcenter server address in => [" + serverIps + "]");
Systems.out("No available configcenter server address in => [" + serverIps + "]");
if (null != ipArray && ipArray.length > 0) {
String svrIpAndPort = ipArray[0].trim();
setEndpoint(svrIpAndPort);
ConfigServer.getInstance().setEndPoint(svrIpAndPort);
Systems.out("No available serverAddress, select {} as default", svrIpAndPort);
}
}
monitorPollingCounter();
}
}
void monitorPollingCounter() {
ExecutorServiceInstance.get(Client.CLIENT_WORKER_ALARM_MONITOR).scheduleAtFixedRate(() -> {
pollingFailedCounter.set(0); // 1分钟内异常计数被重置1次
}, 1L, 60L, TimeUnit.SECONDS);
}
String ensureReady() {
CompositeAppConfigProperties appConf = CompositeAppConfigProperties.getInstance();
boolean isLocal = appConf.boolValue(BootConfig.CONFIGSERVER_ISLOCAL);
env = Objects.requireNotEmpty(appConf.getEnv(), "Application Environment config not found.");
if (isLocal) {
return String.valueOf("127.0.0.1:" + ConfigServer.CONFIG_SERVER_PORT);
}
// 1. 优先从本地拿配置中心地址
String serverIps = appConf.getValue(BootConfig.CONFIGSERVER_ADDRESS);
if (Utils.isNotBlank(serverIps)) {
return serverIps;
}
// 2. 从本地server.properties配置文件中读取
Configuration conf = null;
try {
conf = PropUtil.getConfiguration();
if (null != conf && Utils.isNotBlank(conf.getValue(BootConfig.CONFIGSERVER_ADDRESS))) {
return conf.getValue(BootConfig.CONFIGSERVER_ADDRESS);
}
} catch (Throwable e) {
// throw e;
}
// 3. 从依赖jar包中文件: configserver-env.properties 读取配置中心地址
Properties properties = ClassPath.readConfigFile(BootConfig.JAR_CONFIG_FILE, new Properties());
String thisEnvAddrKey = BootConfig.getServerAddressKey();
if (null != properties && Strings.isNotBlank(properties.getProperty(thisEnvAddrKey))) {
String configServerAddr = properties.getProperty(thisEnvAddrKey);
CompositeAppConfigProperties.getInstance().merge(com.github.javaclub.toolbox.ToolBox.Maps.createStringMap(
BootConfig.CONFIGSERVER_ADDRESS, configServerAddr
));
if (Strings.isBlank(LocalConfigserverConf.getInstance().getServerAddress())) {
LocalConfigserverConf.getInstance().setServerAddress(configServerAddr);
}
return configServerAddr;
}
throw new RuntimeException("Configcenter [" + BootConfig.CONFIGSERVER_ADDRESS + "] is not found.");
}
public void init(int appId, String appkey) {
if (0 >= appId || Strings.isBlank(appkey)) {
return;
}
if (inited.compareAndSet(false, true)) {
ConfigServer.getInstance().setEndPoint(endpoint);
ConfigServer.getInstance().setEnv(env);
ConfigServer.getInstance().setAppid(String.valueOf(appId));
ConfigServer.getInstance().setAppkey(appkey);
ConfigServer.getInstance().init();
Thread thread = new Thread(() -> {
while (true) {
try {
if (AppBootHook.isAppShuttingDown()) {
return;
}
ALARM_BY_MAX_POLLING_FAILED_TIMES = CompositeAppConfigProperties.getInstance().intValue(BootConfig.CONFIGSERVER_CLIENT_POLLING_ALARM, 3);
Thread.sleep(3000L);
if (!appMap.isEmpty()) {
Map > queryParam = new HashMap>();
for (int appid : appMap.keySet()) {
if (0 >= appid) {
continue; // 可能没有配置任何appId
}
AppCfg appCfg = appMap.get(appid);
queryParam.putIfAbsent(appCfg.appid, new HashMap());
synchronized (appCfg.lock) {
for (String configkey : appCfg.configKeyList) {
queryParam.get(appid).put(configkey, "0");
if (cfg.get(appid) != null && cfg.get(appid).get(configkey) != null) {
queryParam.get(appid).put(configkey, cfg.get(appid).get(configkey).timestamp);
}
}
}
}
if (!queryParam.isEmpty()) {
if (polling(queryParam)) { // 配置有更新
for (int tAppId : cfg.keySet()) { // 保存文件快照
ConfigLocalSave.getInstance()
.snapshot(tAppId, appMap.get(tAppId).getAppkey(), cfg.get(tAppId).values());
}
} else {
for (int tAppId : cfg.keySet()) {
// 本地无配置数据时,保存
if (null == ConfigLocalSave.getInstance().readConfigFromFile(tAppId, appMap.get(tAppId).getAppkey())) {
ConfigLocalSave.getInstance()
.snapshot(tAppId, appMap.get(tAppId).getAppkey(), cfg.get(tAppId).values());
}
}
}
}
}
} catch (Throwable e) {
logger.error("", e);
}
}
});
thread.setName("config-center-client");
thread.setDaemon(true);
thread.start();
}
}
// 查询配置是否变更
public boolean polling(Map > param) {
StringBuilder sb = new StringBuilder();
String ipAndPort = ConfigServer.getInstance().getOneServer();
sb.append("http://");
sb.append(ipAndPort);
sb.append("/configcenter/item/query/batch?clientVer=" + getClientVersion());
sb.append("&env=" + env);
if (ServerNodeHolder.getInstance().isNoServersOrAllDown()) {
return false;
}
boolean flag = false;
try {
final Map postBody = new HashMap();
for (int appid : param.keySet()) {
postBody.put(appid, DESCoder.encrypt(JSONObject.toJSONString(param.get(appid)), appMap.get(appid).appkey));
}
String resp = HttpHelper.post(sb.toString(), JSONObject.toJSONString(postBody));
if (Utils.isNotBlank(resp)) {
JSONObject result = JSONObject.parseObject(resp);
if (result.getShort("code") == 0) {
@SuppressWarnings("unchecked")
Map map = JSONObject.parseObject(result.getString("result"), Map.class);
if (map != null && !map.isEmpty()) { // 有变更的值
for (final int appid : map.keySet()) {
String str = DESCoder.decrpty(map.get(appid), appMap.get(appid).appkey);
@SuppressWarnings("unchecked")
final Map cfgMap = JSONObject.parseObject(str, Map.class);
if (cfgMap != null && !cfgMap.isEmpty()) {
cfg.putIfAbsent(appid, new HashMap());
for (String configkey : cfgMap.keySet()) {
JSONObject obj = cfgMap.get(configkey);
CfgVO cfgVO = new CfgVO(obj.getString("configkey"), obj.getString("configvalue"), obj.getString("timestamp"));
cfg.get(appid).put(configkey, cfgVO);
if (cfgVO.isMarkedRemoved()) {
cfg.get(appid).remove(configkey);
logger.warn("ConfigChanged key={} is removed", configkey);
}
}
flag = true;
for (final String configkey : cfgMap.keySet()) {
synchronized (appMap.get(appid).lock) {
JSONObject cfgObj = cfgMap.get(configkey);
String configvalue = cfgObj.getString("configvalue");
String timestamp = cfgObj.getString("timestamp");
ConfigChangedEvent event = new ConfigChangedEvent(configkey, configvalue, timestamp);
// step1: 先触发配置变更事件监听器
if (!appMap.get(appid).listeners.get(configkey).isEmpty()) {
for (final ConfigChangeListener listener : appMap.get(appid).listeners.get(configkey)) {
exector.submit(() -> {
try {
listener.onChange(event);
report(true, appid, appMap.get(appid).appkey, configkey, configvalue);
} catch (Exception e) {
logger.error("", e);
}
});
}
}
// step2: 再处理删除逻辑
if (event.isRemoved()) {
try {
ConfigRemoveFlagHolder.set(false); // 设置不必用同步锁
ConfigcenterLoader.getInstance().removeMonitorKey(configkey);
} finally {
ConfigRemoveFlagHolder.remove();
}
}
}
}
ConfigServer.getInstance().flagAsNormal(ipAndPort);
if (pollingFailedCounter.get() > 0) {
pollingFailedCounter.decrementAndGet();
}
}
}
}
} else {
logger.error("Failed to getConfig: " + resp);
}
}
} catch (Exception e) {
logger.error("ConfigCenterClient polling {} failed, appId={}", ipAndPort, JSON.toJSONString(param.keySet()));
pollingFailedCounter.incrementAndGet();
ConfigServer.getInstance().flagAsBroken(ipAndPort, System.currentTimeMillis());
if (pollingFailedCounter.get() > ALARM_BY_MAX_POLLING_FAILED_TIMES) {
ExecutorServiceInstance.get(Client.CLIENT_WORKER_ALARM_MONITOR).submit(() -> {
Map map = Maps.newHashMap();
map.put("请求地址", sb.toString());
map.put("异常明细", Utils.lessText(e.getMessage(), 60, true));
DingTalkUtils.sendMarkdown("ConfigCenterClient轮询异常", new Date(), map);
});
}
}
return flag;
}
@Override
public String getConfig(int appid, String appkey, String configkey, ConfigChangeListener configListener) {
if (!inited.get()) {
init(appid, appkey);
}
if (0 >= appid || Strings.isBlank(appkey)) {
return null;
}
appMap.putIfAbsent(appid, new AppCfg());
appMap.get(appid).init(appid, appkey);
String value = null;
try {
if (null != cfg.get(appid) && null != cfg.get(appid).get(configkey)
&& cfg.get(appid).get(configkey).isRecentlyFreshed()) {
value = cfg.get(appid).get(configkey).configvalue;
logger.info("ConfigKey={} present by localMemory\t timestamp={}", configkey, cfg.get(appid).get(configkey).parseTimestamp());
return value;
}
if (ServerNodeHolder.getInstance().isNoServersOrAllDown()) { // 快速失败,走本地配置容灾
throw new ConfigException("No available ConfigCenter ServerNodes");
}
value = queryConfig(appid, appkey, configkey);
} catch (Exception e) {
if (!ServerNodeHolder.getInstance().isNoServersOrAllDown()) {
logger.warn("Failed to get config: configkey[{}], appid[{}]", configkey, appid);
}
// throw new ConfigException(String.format("Failed to get config: appid[%s], configkey[%s]", appid, configkey));
} finally {
if (null == value) {
Map map = ConfigLocalSave.getInstance().get(appid, appkey, configkey);
if (null != map) {
value = map.get("value");
if (logger.isDebugEnabled()) {
logger.debug("Loading configValue from localcache: appid={}, key={}, value={}, timestamp={}",
appid, configkey, Strings.filter(value),
ThreadLocalDateFormatter.timestampFormat(new Date(Numbers.parseLong(map.get("timestamp")))));
}
}
}
appMap.get(appid).addConfigKey(configkey, configListener); // 注册配置项及监听器
if (null != value) {
if (Client.isClientAppInited()) { // 应用刚启动时刻, 不上报配置信息
report(true, appid, appkey, configkey, value);
}
} else {
logger.warn("Perhaps no configKey[{}] in appid[{}], query value is null", configkey, appid);
// throw new ConfigException(String.format("failed to get config: appid[%s],configkey[%s]", appid, configkey));
}
}
return value;
}
public String queryMeta(int appid, String appkey, boolean cdl, String topic) throws ConfigException {
String apiPath = "/configcenter/config/queryMeta";
Map queryParams = ImmutableMap.of("dataTopic", topic, "cdl", String.valueOf(cdl));
try {
String resp = null;
try {
resp = doQuery(apiPath, appid, queryParams);
} catch (Exception e) {
}
if(Utils.isBlank(resp)) { // retry
Thread.sleep(200L);
resp = doQuery(apiPath, appid, queryParams);
}
// 响应正常
if (Utils.isNotBlank(resp)) {
JSONObject result = JSONObject.parseObject(resp);
if (result.getShort("code") == 0) {
if (Utils.isNotBlank(result.getString("result"))) {
String value = DESCoder.decrpty(result.getString("result"), appkey);
if (Utils.isNotBlank(value)) {
return value;
}
}
}
}
} catch (Exception e) {
throw new ConfigException(e.getMessage(), e);
}
return null;
}
private String queryConfig(int appid, String appkey, String configkey) throws ConfigException {
try {
String resp = null;
configkey = configkey.trim();
appkey = appkey.trim();
Map params = ImmutableMap.of("key", configkey);
String apiPath = "/configcenter/item/query";
try {
resp = doQuery(apiPath, appid, params);
} catch (Exception e) {
}
if (Utils.isBlank(resp)) { // retry
Thread.sleep(Numbers.random(5L, 30L));
resp = doQuery(apiPath, appid, params);
}
if (Utils.isNotBlank(resp)) {
JSONObject result = JSONObject.parseObject(resp);
if (result.getShort("code") == 0) {
if (Utils.isNotBlank(result.getString("result"))) {
String v = DESCoder.decrpty(result.getString("result"), appkey);
if (Utils.isNotBlank(v)) {
@SuppressWarnings("unchecked")
Map map = JSONObject.parseObject(v, Map.class);
String value = map.get("value");
String timestamp = map.get("timestamp");
CfgVO cfgVO = new CfgVO(configkey, value, timestamp);
cfg.putIfAbsent(appid, new HashMap());
cfg.get(appid).put(configkey, cfgVO);
return value;
}
}
} else {
logger.error("Failed to getConfig: " + resp);
throw new ConfigException(resp);
}
}
} catch (Exception e) {
throw new ConfigException(e.getMessage(), e);
}
return null;
}
public String doQueryKeys(int appId) throws Exception {
final StringBuilder sb = new StringBuilder();
String currentApp = CompositeAppConfigProperties.getInstance().getAppName();
sb.append("http://");
sb.append(ConfigServer.getInstance().getOneServer());
sb.append("/configcenter/item/queryKeys?clientVer=" + getClientVersion());
sb.append("&env=" + getEnv());
sb.append("&appid=" + appId);
sb.append(Utils.isBlank(currentApp) ? "" : ("&appName=" + currentApp));
if (ServerNodeHolder.getInstance().isNoServersOrAllDown()) {
// 本地容灾处理
String resp = ConfigLocalSave.getInstance().readLocalAppConfigKeys(appId);
logger.warn("ConfigCenter read AppConfigKeys from localcache backup, appId={}", appId);
return resp;
}
Future future = exector.submit(new Callable() {
@Override
public String call() throws Exception {
String resp = HttpHelper.get(sb.toString());
return resp;
}
});
return future.get(Numbers.random(3, 5), TimeUnit.SECONDS);
}
public void doPost(boolean async, String appid, String appkey, String topic,
Map urlParams, String postJson) throws Exception {
String currentAppName = CompositeAppConfigProperties.getInstance().getAppName();
final StringBuilder sb = new StringBuilder();
sb.append("http://{}");
sb.append("/configcenter/applyRequest?clientVer=" + getClientVersion());
sb.append("&env=" + getEnv());
sb.append("&appid=" + appid);
if (Utils.isNotBlank(currentAppName)) {
sb.append("&appName=" + currentAppName);
}
sb.append("&requestTopic=" + topic);
if(null != urlParams && !urlParams.isEmpty()) {
for (Map.Entry elem : urlParams.entrySet()) {
if (Utils.isNotBlank(elem.getKey())) {
sb.append(Utils.concat("&", elem.getKey(), "=", HttpHelper.urlencode(elem.getValue())));
}
}
}
String urlFormat = sb.toString();
int retry = 3;
while (retry-- > 0) {
try {
final String text = DESCoder.encrypt(postJson, appkey);
Future future = exector.submit(new Callable() {
@Override
public String call() throws Exception {
String url = Strings.format(urlFormat, ConfigServer.getInstance().getOneServer());
return HttpHelper.post(url, text);
}
});
if (!async) { // 设置了同步,才会多次执行
if ("true".equals(future.get(3, TimeUnit.SECONDS))) {
return;
}
} else { // 设置了异步,仅会执行1次
break;
}
} catch (Exception e) {
logger.error("", e);
}
}
}
private String doQuery(String apiPath, int appid, Map queryParams) throws Exception {
String currentApp = CompositeAppConfigProperties.getInstance().getAppName();
final StringBuilder sbf = new StringBuilder();
sbf.append("http://").append(ConfigServer.getInstance().getOneServer());
sbf.append(apiPath + "?clientVer=" + getClientVersion());
sbf.append("&env=" + getEnv());
sbf.append("&appid=" + appid);
sbf.append(Utils.isBlank(currentApp) ? "" : ("&appName=" + currentApp));
if(null != queryParams && !queryParams.isEmpty()) {
for (Map.Entry elem : queryParams.entrySet()) {
if (Utils.isNotBlank(elem.getKey())) {
sbf.append(Utils.concat("&", elem.getKey(), "=", HttpHelper.urlencode(elem.getValue())));
}
}
}
Future future = exector.submit(new Callable() {
@Override
public String call() throws Exception {
String resp = HttpHelper.get(sbf.toString());
return resp;
}
});
return future.get(6, TimeUnit.SECONDS);
}
public void report(boolean async, int appid, String appkey, String configkey, String configValue) {
String currentAppName = CompositeAppConfigProperties.getInstance().getAppName();
int retry = 2;
while (retry-- > 0) {
final StringBuilder sb = new StringBuilder();
sb.append("http://");
sb.append(ConfigServer.getInstance().getOneServer());
sb.append("/configcenter/report?clientVer=" + getClientVersion());
sb.append("&env=" + getEnv());
sb.append("&appid=" + appid);
sb.append("&configKey=" + configkey);
if (Utils.isNotBlank(currentAppName)) {
sb.append("&appName=" + currentAppName);
}
try {
final String text = DESCoder.encrypt(configValue, appkey);
Future future = exector.submit(new Callable() {
@Override
public String call() throws Exception {
return HttpHelper.post(sb.toString(), text);
}
});
if (!async) {
if ("true".equals(future.get(3, TimeUnit.SECONDS))) {
return;
}
} else {
break;
}
} catch (Exception e) {
logger.error("", e);
}
}
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
DefaultConfigCenterClient.endpoint = endpoint;
}
@Override
public String getEnv() {
return DefaultConfigCenterClient.env;
}
public AppCfg getAppCfg(int appId) {
if (null != appMap) {
return appMap.get(appId);
}
return null;
}
@Override
public boolean removeConfig(int appId, String appKey, String configKey) {
if (null != cfg && null != cfg.get(appId)) {
if (cfg.get(appId).containsKey(configKey)) {
cfg.get(appId).remove(configKey);
}
}
if (null != appMap && null != appMap.get(appId)) {
appMap.get(appId).removeConfigKey(configKey);
}
return true;
}
@Override
public boolean setConfig(int appid, String appkey, String configKey, String configValue) throws ConfigException {
try {
if (!inited.get()) {
init(appid, appkey);
}
final StringBuilder sb = new StringBuilder();
sb.append("http://");
sb.append(ConfigServer.getInstance().getOneServer());
sb.append("/configcenter/item/update?clientVer=" + getClientVersion());
sb.append("&env=" + getEnv());
sb.append("&appid=" + appid);
Map map = new HashMap();
map.put("key", configKey);
map.put("value", configValue);
final String text = DESCoder.encrypt(JSONObject.toJSONString(map), appkey);
Future future = exector.submit(new Callable() {
@Override
public String call() throws Exception {
String resp = HttpHelper.post(sb.toString(), text);
return resp;
}
});
return "success".equals(future.get(3, TimeUnit.SECONDS));
} catch (Exception e) {
throw new ConfigException(e);
}
}
/**
* 取当前客户端在使用的版本
*
* @return configcenter-client版本号
*/
public static String getClientVersion() {
return Utils.getConfigClientVer();
}
public static void main(String[] args) {
//
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy