Merge pull request #110 from nutzam/add_nutzboot-starter-config-client

add: starter-config-client NB配置中心客户端
This commit is contained in:
Wendal Chen 2018-01-18 14:52:19 +08:00 committed by GitHub
commit 491554aeae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 369 additions and 15 deletions

View File

@ -5,6 +5,7 @@
* 变更:
* add: starter-tio by [蛋蛋](https://github.com/TopCoderMyDream)
* add: starter-apollo-client 对接apollo配置中心. apollo是携程框架部门研发的分布式配置中心
* add: starter-config-client NB配置中心的客户端,其服务端可以是任意支持Restful的服务器
* add: feign支持从ioc容器获取client/encoder/decoder,且自定义JsonFormat
* add: starter-eureka-server新版的status页面 by [温泉](https://github.com/ywjno)
* update: 更新到HikariCP 2.7.5

View File

@ -144,8 +144,9 @@ public class MainLauncher {
- [x] starter-[eureka-server](https://github.com/Netflix/eureka) 服务治理的服务器端
- [x] starter-[eureka-client](https://github.com/Netflix/eureka) 服务治理的客户端
- 配置中心
- [ ] NB Config Server/Client
- [x] starter-[apollo-client](https://github.com/ctripcorp/apollo) 携程框架部门研发的分布式配置中心
- [ ] NB Config Server 配置中心的服务端
- [x] starter-config-client NB Config Client 配置中心的客户端
- [x] starter-[apollo-client](https://github.com/ctripcorp/apollo) 携程框架部门研发的分布式配置中心的客户端
- API网关
- [ ] NB API网关
- [ ] zuul

View File

@ -46,11 +46,7 @@ public class PropertiesConfigureLoader extends AbstractConfigureLoader {
}
if (flag) {
// 加载application.properties
try (InputStream ins = resourceLoader.get(path)) {
if (ins != null) {
conf.load(Streams.utf8r(ins), false);
}
}
readPropertiesPath(path);
}
// 也许命令行里面指定了profile,需要提前load进来
PropertiesProxy tmp = new PropertiesProxy();
@ -66,12 +62,8 @@ public class PropertiesConfigureLoader extends AbstractConfigureLoader {
// 加载指定profile,如果有的话
if (conf.has("nutz.profiles.active")) {
String profile = conf.get("nutz.profiles.active");
path = path.substring(0, path.lastIndexOf('.')) + "-" + profile + ".properties";
try (InputStream ins = resourceLoader.get(path)) {
if (ins != null) {
conf.load(Streams.utf8r(ins), false);
}
}
String _path = path.substring(0, path.lastIndexOf('.')) + "-" + profile + ".properties";
readPropertiesPath(_path);
}
// 如果conf内含有nutz.boot.configure.properties.dir配置则读取该目录下的所有配置文件
// 配置示例 nutz.boot.configure.properties.dir=config, 那么读取的就是jar包当前目录下config子目录下的所有properties文件
@ -120,4 +112,12 @@ public class PropertiesConfigureLoader extends AbstractConfigureLoader {
}
return path;
}
protected void readPropertiesPath(String path) throws IOException {
try (InputStream ins = resourceLoader.get(path)) {
if (ins != null) {
conf.load(Streams.utf8r(ins), false);
}
}
}
}

View File

@ -0,0 +1,56 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-demo-simple</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<artifactId>nutzboot-demo-simple-config-client</artifactId>
<dependencies>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-nutz-mvc</artifactId>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/nutz/org.nutz.boot.starter.NbStarter</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.nutz.demo.simple.MainLauncher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,44 @@
package io.nutz.demo.simple;
import org.nutz.boot.NbApp;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
/**
* 注意, 本demo需要配合配置中心的服务端使用, 为了演示, 使用的是 http://nbconfig.nutz.cn/ <p/>
*
* 服务端可以是nginx/apache等一切静态文件服务器, 也可以是nutz mvc/nutz boot app等一切能提供web服务的进程
* @author wendal
*
*/
@IocBean
public class MainLauncher {
/*
本demo的一些说明:
1. META-INF/app.properties 是可选文件, 其中的参数用于拼接URL,规则是
http://${config.hosts}/${config.zone}/${app.id}/application.properties
http://${config.hosts}/${config.zone}/${app.id}/application-dev.properties
http://${config.hosts}/${config.zone}/${app.id}/application-prod.properties
里面的参数也可以通过 java -Dapp.id=XXX -Dconfig.hosts=YYY myjar.jar 进行设置
2. config.hosts可以是多个域名,用逗号分隔
3. 安全机制是可选的,默认是http basic auth, ${config.zone}作为用户名, ${config.password}作为密码
*/
@Inject
protected PropertiesProxy conf;
@Ok("json:full")
@At("/config/getall")
public PropertiesProxy getAll() {
return conf;
}
public static void main(String[] args) throws Exception {
new NbApp().setPrintProcDoc(true).run();
}
}

View File

@ -0,0 +1,5 @@
app.id=SampleApp
config.zone=guest
config.token=123456
config.password=ABC123456
config.hosts=nbconfig.nutz.cn

View File

@ -0,0 +1,7 @@
log4j.rootLogger=debug,Console
log4j.logger.org.eclipse.jetty=info
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] %d{HH:mm:ss.SSS} %l - %m%n

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello, So NB!</title>
</head>
<body>
Hello, So NB!
</body>
</html>

View File

@ -10,7 +10,7 @@
<artifactId>nutzboot-demo-simple-tio</artifactId>
<packaging>jar</packaging>
<name>nutzboot-demo-simple.tio</name>
<name>nutzboot-demo-simple-tio</name>
<url>http://maven.apache.org</url>
<properties>

View File

@ -2,15 +2,18 @@ package org.nutz.boot.starter.apollo;
import org.nutz.boot.AppContext;
import org.nutz.boot.starter.ServerFace;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
@IocBean
public class ApolloConfigureChangeStarter implements ServerFace {
public class ApolloConfigureChangeStarter implements ServerFace, ConfigChangeListener {
@Inject
protected AppContext appContext;
@ -20,4 +23,25 @@ public class ApolloConfigureChangeStarter implements ServerFace {
appContext.getBeans(ConfigChangeListener.class).forEach((listener) -> config.addChangeListener(listener));
}
@Override
public void onChange(ConfigChangeEvent changeEvent) {
PropertiesProxy conf = appContext.getConf();
for (String key : changeEvent.changedKeys()) {
ConfigChange cc = changeEvent.getChange(key);
switch (cc.getChangeType()) {
case ADDED:
conf.put(key, cc.getNewValue());
break;
case MODIFIED:
conf.put(key, cc.getNewValue());
break;
case DELETED:
conf.remove(key);
break;
default:
break;
}
}
}
}

View File

@ -0,0 +1,16 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<artifactId>nutzboot-starter-config-client</artifactId>
<packaging>jar</packaging>
<name>nutzboot-starter-config-client</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<description>NutzBoot, micoservice base on Nutz</description>
</project>

View File

@ -0,0 +1,127 @@
package org.nutz.cloud.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.nutz.cloud.config.spi.ConfigureEventHandler;
import org.nutz.http.Request;
import org.nutz.http.Request.METHOD;
import org.nutz.http.Response;
import org.nutz.http.Sender;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Logs;
import org.nutz.repo.Base64;
public class CloudConfig {
protected static Properties selfConf;
public static void init() {
Properties selfConf = new Properties();
InputStream ins = CloudConfig.class.getClassLoader().getResourceAsStream("META-INF/app.properties");
if (ins != null) {
try {
selfConf.load(ins);
selfConf.putIfAbsent("app.id", "SampleApp");
selfConf.putIfAbsent("config.zone", "guest");
selfConf.putIfAbsent("config.token", "123456");
selfConf.putIfAbsent("config.label", "HEAD");
selfConf.putIfAbsent("config.type", "simple");
selfConf.putIfAbsent("config.hosts", "nbconfig.nutz.cn");
}
catch (IOException e) {
throw new RuntimeException("META-INF/app.properties can't read!", e);
}
}
CloudConfig.selfConf = selfConf;
}
public static void fromRemote(PropertiesProxy conf, String fileName) {
NutMap params = new NutMap();
params.put("id", getSelfConfig("app.id", null));
params.put("zone", getSelfConfig("app.zone", "guest"));
params.put("token", getSelfConfig("config.token", "123456"));
params.put("label", getSelfConfig("config.label", "HEAD"));
params.put("password", getSelfConfig("config.password", "ABC123456"));
String hosts = getSelfConfig("config.hosts", null);
String type = getSelfConfig("config.type", "simple");
for (int i = 0; i < 5; i++) {
for (String host : Strings.splitIgnoreBlank(hosts)) {
try {
switch (type) {
// spring-cloud-config-server
case "spring":
bySpring(params, host);
return;
// 访问 eureka 获取服务器列表,然后按simple处理
case "eureku":
byEureka(params, host);
return;
// 简单模式, 也许就是nutzcloud-config-server的模块, 按路径取就行啦
case "simple":
default:
bySimple(conf, params, host, fileName);
return;
}
}
catch (Throwable e) {
Logs.get().infof("fail at type=%s host=%s", type, host);
}
}
}
throw new RuntimeException("can't fetch config from remote");
}
protected static void bySimple(PropertiesProxy conf, NutMap params, String host, String fileName) {
String url = String.format("http://%s/%s/%s/%s", host, params.get("zone"), params.get("id"), fileName);
try {
Request req = Request.create(url, METHOD.GET);
long now = System.currentTimeMillis();
String once = R.UU32();
String sign = Lang.sha1(String.format("%s,%s,%s,%s,%s", params.get("zone"), params.get("id"), now, params.get("token"), once));
req.getHeader().set("X-Client-Once", once);
req.getHeader().set("X-Client-Time", now + "");
req.getHeader().set("X-Client-Sign", sign);
req.getHeader().set("Authorization", Base64.encodeToString((params.get("zone") + ":" + params.getString("password")).getBytes(), false));
Response resp = Sender.create(req).setTimeout(3000).send();
if (resp.isOK()) {
conf.load(resp.getReader());
return;
}
else {
Logs.get().warnf("url=%s code=%s", url, resp.getStatus());
}
}
catch (Exception e) {
Logs.get().warn("url=" + url, e);
}
throw new RuntimeException("FAILED url=" + url);
}
protected static PropertiesProxy bySpring(NutMap params, String host) {
throw Lang.noImplement();
}
protected static PropertiesProxy byEureka(NutMap params, String host) {
throw Lang.noImplement();
}
public static void addListener(ConfigureEventHandler listener) {
// TODO 完成事件监听
}
protected static String getSelfConfig(String key, String dftValue) {
// 优先 -Dapp.id 然后是环境变量, 然后是内置的app.properties
String value = System.getProperty(key, System.getenv(key));
if (Strings.isBlank(value)) {
value = selfConf.getProperty(key, dftValue);
}
return value;
}
}

View File

@ -0,0 +1,19 @@
package org.nutz.cloud.config;
import org.nutz.boot.AppContext;
import org.nutz.boot.starter.ServerFace;
import org.nutz.cloud.config.spi.ConfigureEventHandler;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean
public class CloudConfigureChangeStarter implements ServerFace {
@Inject
protected AppContext appContext;
public void start() throws Exception {
appContext.getBeans(ConfigureEventHandler.class).forEach((listener)->CloudConfig.addListener(listener));
}
}

View File

@ -0,0 +1,21 @@
package org.nutz.cloud.config;
import java.io.IOException;
import org.nutz.boot.config.impl.PropertiesConfigureLoader;
public class CloudConfigureLoader extends PropertiesConfigureLoader {
public CloudConfigureLoader() {
CloudConfig.init();
}
protected void readPropertiesPath(String path) throws IOException {
if (path.contains("/") || path.contains("\\"))
super.readPropertiesPath(path);
else {
CloudConfig.fromRemote(conf, path);
}
}
}

View File

@ -0,0 +1,15 @@
package org.nutz.cloud.config.spi;
import java.util.EventListener;
import java.util.List;
public interface ConfigureEventHandler extends EventListener {
void trigger(List<KeyEvent> events);
class KeyEvent {
public String name;
public String value;
public String action;
}
}

View File

@ -0,0 +1 @@
org.nutz.cloud.config.CloudConfigureLoader

View File

@ -0,0 +1 @@
org.nutz.cloud.config.CloudConfigureChangeStarter

View File

@ -53,6 +53,7 @@
<module>nutzboot-starter-rabbitmq</module>
<module>nutzboot-starter-tio</module>
<module>nutzboot-starter-apollo-client</module>
<module>nutzboot-starter-config-client</module>
</modules>
<dependencies>
<dependency>

View File

@ -649,6 +649,11 @@
<artifactId>nutzboot-starter-apollo-client</artifactId>
<version>${nutzboot.version}</version>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-config-client</artifactId>
<version>${nutzboot.version}</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>