deason 4 anos atrás
commit
1ee6eb24ef
25 arquivos alterados com 997 adições e 0 exclusões
  1. 50 0
      .gitignore
  2. 81 0
      config-center-client/pom.xml
  3. 32 0
      config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/ConfigCenterClientAutoConfiguration.java
  4. 24 0
      config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/ConfigConstants.java
  5. 44 0
      config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/ConfigRefreshEndPoint.java
  6. 41 0
      config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/CustomConfigProcessor.java
  7. 51 0
      config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/CustomEnvironmentPostProcessor.java
  8. 27 0
      config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/CustomMapPropertySource.java
  9. 76 0
      config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/CustomSpringApplicationRunListener.java
  10. 88 0
      config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/RemotePropertyLoader.java
  11. 88 0
      config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/utils/PropertyUtils.java
  12. 6 0
      config-center-client/src/main/resources/META-INF/spring.factories
  13. 82 0
      config-center-server/pom.xml
  14. 18 0
      config-center-server/src/main/java/cn/com/qmth/framework/config/center/ConfigCenterApplication.java
  15. 55 0
      config-center-server/src/main/java/cn/com/qmth/framework/config/center/controller/ConfigCenterController.java
  16. 19 0
      config-center-server/src/main/java/cn/com/qmth/framework/config/center/controller/IndexController.java
  17. 38 0
      config-center-server/src/main/java/cn/com/qmth/framework/config/center/core/ControllerAdviceHandler.java
  18. 88 0
      config-center-server/src/main/java/cn/com/qmth/framework/config/center/utils/PropertyUtils.java
  19. 8 0
      config-center-server/src/main/resources/application.properties
  20. 5 0
      config-center-server/src/main/resources/examcloud/dev/application-demo.properties
  21. 5 0
      config-center-server/src/main/resources/examcloud/dev/application.properties
  22. 5 0
      config-center-server/src/main/resources/examcloud/prod/application-demo.properties
  23. 5 0
      config-center-server/src/main/resources/examcloud/prod/application.properties
  24. 46 0
      config-center-server/src/main/resources/logback-spring.xml
  25. 15 0
      pom.xml

+ 50 - 0
.gitignore

@@ -0,0 +1,50 @@
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+*.class
+*.log
+
+
+### Eclipse & STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+
+### VS Code ###
+.vscode
+node_modules
+package-lock.json
+yarn.lock
+
+
+### Package Files ###
+*.zip
+*.war
+*.ear
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+target/
+
+.flattened-pom.xml
+.DS_Store
+

+ 81 - 0
config-center-client/pom.xml

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>cn.com.qmth.framework</groupId>
+    <artifactId>config-center-client</artifactId>
+    <packaging>jar</packaging>
+    <version>1.0.0</version>
+
+    <properties>
+        <spring-boot.version>2.3.10.RELEASE</spring-boot.version>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                    <skipTests>true</skipTests>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 32 - 0
config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/ConfigCenterClientAutoConfiguration.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:25
+ */
+
+package cn.com.qmth.framework.config.center.client;
+
+import cn.com.qmth.framework.config.center.client.core.ConfigConstants;
+import cn.com.qmth.framework.config.center.client.core.ConfigRefreshEndPoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author: Deason
+ * @since: 2021/4/21
+ */
+@Configuration
+@ConditionalOnProperty(ConfigConstants.CONFIG_CENTER_ENABLED)
+public class ConfigCenterClientAutoConfiguration {
+
+    private static final Logger log = LoggerFactory.getLogger(ConfigCenterClientAutoConfiguration.class);
+
+    @Bean
+    public ConfigRefreshEndPoint configRefreshEndPoint() {
+        log.info("configRefreshEndPoint init...");
+        return new ConfigRefreshEndPoint();
+    }
+
+}

+ 24 - 0
config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/ConfigConstants.java

@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:25
+ */
+
+package cn.com.qmth.framework.config.center.client.core;
+
+public interface ConfigConstants {
+
+    String SPRING_PROFILES_ACTIVE = "spring.profiles.active";
+
+    String CONFIG_CENTER_ENABLED = "sys.config.center.enabled";
+
+    String SYS_CONFIG_CENTER_ADDRESS = "sys.config.center.address";
+
+    String SYS_CONFIG_CENTER_NAMESPACE = "sys.config.center.namespace";
+
+    String CONFIG_CENTER_APP_CODE = "sys.config.center.appCode";
+
+    String CUSTOM_PROPERTY_SOURCE = "configCenterProperties";
+
+    String DEFAULT_PROPERTIES = "application.properties";
+
+}

+ 44 - 0
config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/ConfigRefreshEndPoint.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:25
+ */
+
+package cn.com.qmth.framework.config.center.client.core;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+import java.util.Map;
+
+/**
+ * @author: Deason
+ * @since: 2021/4/21
+ */
+@Endpoint(id = "config-refresh")
+public class ConfigRefreshEndPoint {
+
+    private static final Logger log = LoggerFactory.getLogger(ConfigRefreshEndPoint.class);
+
+    @Autowired
+    private ConfigurableEnvironment environment;
+
+    @ReadOperation
+    public String configRefreshEndPoint() {
+        Map<String, Object> remoteProperties = RemotePropertyLoader.call(
+                environment.getProperty(ConfigConstants.SYS_CONFIG_CENTER_ADDRESS),
+                environment.getProperty(ConfigConstants.SYS_CONFIG_CENTER_NAMESPACE),
+                environment.getProperty(ConfigConstants.CONFIG_CENTER_APP_CODE),
+                environment.getProperty(ConfigConstants.SPRING_PROFILES_ACTIVE)
+        );
+
+        CustomMapPropertySource customMapPropertySource = new CustomMapPropertySource(ConfigConstants.CUSTOM_PROPERTY_SOURCE, remoteProperties);
+        environment.getPropertySources().addLast(customMapPropertySource);
+
+        return "finished";
+    }
+
+}

+ 41 - 0
config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/CustomConfigProcessor.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:25
+ */
+
+package cn.com.qmth.framework.config.center.client.core;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.env.OriginTrackedMapPropertySource;
+import org.springframework.core.env.MutablePropertySources;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.env.SimpleCommandLinePropertySource;
+
+public interface CustomConfigProcessor {
+
+    default boolean isWebApplication(SpringApplication application) {
+        return WebApplicationType.SERVLET == application.getWebApplicationType();
+    }
+
+    default void printPropertySources(MutablePropertySources sources) {
+        sources.forEach(e -> {
+            if (e instanceof SimpleCommandLinePropertySource) {
+                this.printProperties(e, ((SimpleCommandLinePropertySource) e).getPropertyNames());
+            } else if (e instanceof OriginTrackedMapPropertySource) {
+                this.printProperties(e, ((OriginTrackedMapPropertySource) e).getPropertyNames());
+            } else if (e instanceof CustomMapPropertySource) {
+                this.printProperties(e, ((CustomMapPropertySource) e).getPropertyNames());
+            }
+        });
+    }
+
+    default void printProperties(PropertySource propertySource, String[] propertyNames) {
+        System.out.println("\n****************************** " + propertySource.getName());
+        for (String propertyName : propertyNames) {
+            System.out.println(propertyName + "=" + propertySource.getProperty(propertyName));
+        }
+        System.out.println("****************************** \n");
+    }
+
+}

+ 51 - 0
config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/CustomEnvironmentPostProcessor.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:25
+ */
+
+package cn.com.qmth.framework.config.center.client.core;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+import java.util.Map;
+
+/**
+ * @author: Deason
+ * @since: 2021/4/21
+ */
+public class CustomEnvironmentPostProcessor implements CustomConfigProcessor, EnvironmentPostProcessor, Ordered {
+
+    @Override
+    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
+        if (!this.isWebApplication(application)) {
+            return;
+        }
+
+        Boolean configCenterEnabled = environment.getProperty(ConfigConstants.CONFIG_CENTER_ENABLED, Boolean.class, false);
+        if (!configCenterEnabled) {
+            return;
+        }
+
+        // 获取远程配置中心的配置项
+        Map<String, Object> remoteProperties = RemotePropertyLoader.call(
+                environment.getProperty(ConfigConstants.SYS_CONFIG_CENTER_ADDRESS),
+                environment.getProperty(ConfigConstants.SYS_CONFIG_CENTER_NAMESPACE),
+                environment.getProperty(ConfigConstants.CONFIG_CENTER_APP_CODE),
+                environment.getProperty(ConfigConstants.SPRING_PROFILES_ACTIVE)
+        );
+
+        CustomMapPropertySource customMapPropertySource = new CustomMapPropertySource(ConfigConstants.CUSTOM_PROPERTY_SOURCE, remoteProperties);
+        environment.getPropertySources().addLast(customMapPropertySource);
+
+        this.printPropertySources(environment.getPropertySources());
+    }
+
+    @Override
+    public int getOrder() {
+        return LOWEST_PRECEDENCE;
+    }
+
+}

+ 27 - 0
config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/CustomMapPropertySource.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:25
+ */
+
+package cn.com.qmth.framework.config.center.client.core;
+
+import org.springframework.core.env.MapPropertySource;
+
+import java.util.Map;
+
+/**
+ * @author: Deason
+ * @since: 2021/4/21
+ */
+public class CustomMapPropertySource extends MapPropertySource {
+
+    public CustomMapPropertySource(String name, Map<String, Object> source) {
+        super(name, source);
+    }
+
+    @Override
+    public Object getProperty(String name) {
+        return this.source.get(name);
+    }
+
+}

+ 76 - 0
config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/CustomSpringApplicationRunListener.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:25
+ */
+
+package cn.com.qmth.framework.config.center.client.core;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.SpringApplicationRunListener;
+import org.springframework.boot.logging.DeferredLog;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+/**
+ * @author: Deason
+ * @since: 2021/4/21
+ */
+public class CustomSpringApplicationRunListener implements CustomConfigProcessor, SpringApplicationRunListener, Ordered {
+
+    private static final DeferredLog log = new DeferredLog();
+
+    private final SpringApplication application;
+
+    private final String[] args;
+
+    public CustomSpringApplicationRunListener(SpringApplication application, String[] args) {
+        this.application = application;
+        this.args = args;
+    }
+
+    @Override
+    public void started(ConfigurableApplicationContext context) {
+        if (!this.isWebApplication(application)) {
+            return;
+        }
+
+        this.notice(context.getEnvironment(), "started");
+
+        log.debug("started...");
+        log.replayTo(CustomSpringApplicationRunListener.class);
+    }
+
+    @Override
+    public void failed(ConfigurableApplicationContext context, Throwable exception) {
+        if (!this.isWebApplication(application)) {
+            return;
+        }
+
+        this.notice(context.getEnvironment(), "failed");
+
+        log.debug("failed...");
+        log.replayTo(CustomSpringApplicationRunListener.class);
+    }
+
+    private void notice(ConfigurableEnvironment environment, String message) {
+        Boolean configCenterEnabled = environment.getProperty(ConfigConstants.CONFIG_CENTER_ENABLED, Boolean.class, false);
+        if (!configCenterEnabled) {
+            return;
+        }
+
+        RemotePropertyLoader.notice(
+                environment.getProperty(ConfigConstants.SYS_CONFIG_CENTER_ADDRESS),
+                environment.getProperty(ConfigConstants.SYS_CONFIG_CENTER_NAMESPACE),
+                environment.getProperty(ConfigConstants.CONFIG_CENTER_APP_CODE),
+                environment.getProperty(ConfigConstants.SPRING_PROFILES_ACTIVE),
+                message
+        );
+    }
+
+    @Override
+    public int getOrder() {
+        return HIGHEST_PRECEDENCE;
+    }
+
+}

+ 88 - 0
config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/core/RemotePropertyLoader.java

@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:25
+ */
+
+package cn.com.qmth.framework.config.center.client.core;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import okhttp3.*;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author: Deason
+ * @since: 2021/4/21
+ */
+public class RemotePropertyLoader {
+
+    private static final Logger log = LoggerFactory.getLogger(RemotePropertyLoader.class);
+
+    private static final String CONTENT_TYPE = "application/json; charset=UTF-8";
+
+    public static Map<String, Object> call(String address, String namespace, String appCode, String profile) {
+        if (StringUtils.isEmpty(address)) {
+            log.warn("{} is not exist", ConfigConstants.SYS_CONFIG_CENTER_ADDRESS);
+            System.exit(-1);
+        }
+
+        if (StringUtils.isEmpty(namespace)) {
+            log.warn("{} is not exist", ConfigConstants.SYS_CONFIG_CENTER_NAMESPACE);
+            System.exit(-1);
+        }
+
+        if (StringUtils.isEmpty(appCode)) {
+            log.warn("{} is not exist", ConfigConstants.CONFIG_CENTER_APP_CODE);
+            System.exit(-1);
+        }
+
+        if (StringUtils.isEmpty(profile)) {
+            log.warn("{} is not exist", ConfigConstants.SPRING_PROFILES_ACTIVE);
+            System.exit(-1);
+        }
+
+        String configCenterUrl = String.format("http://%s/config/center/pull/%s/%s/%s", address, namespace, appCode, profile);
+        log.info("ConfigCenter call url {}", configCenterUrl);
+
+        RequestBody formBody = FormBody.create(MediaType.parse(CONTENT_TYPE), "{}");
+        Request.Builder request = new Request.Builder().url(configCenterUrl).post(formBody);
+
+        try (Response response = new OkHttpClient.Builder().build().newCall(request.build()).execute();
+             ResponseBody body = response.body();) {
+            String resp = body.string();
+
+            if (response.isSuccessful()) {
+                ObjectMapper jsonMapper = new ObjectMapper();
+                JavaType javaType = jsonMapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class);
+                return jsonMapper.readValue(resp, javaType);
+            }
+
+            log.error("ConfigCenter call fail... {}", resp);
+        } catch (Exception e) {
+            log.error("ConfigCenter call err! {}", e.getMessage(), e);
+        }
+
+        return new HashMap<>();
+    }
+
+    public static void notice(String address, String namespace, String appCode, String profile, String msg) {
+        String configCenterUrl = String.format("http://%s/config/center/notice/%s/%s/%s?msg=%s",
+                address, namespace, appCode, profile, msg);
+        log.info("ConfigCenter notice url {}", configCenterUrl);
+
+        RequestBody formBody = FormBody.create(MediaType.parse(CONTENT_TYPE), "{}");
+        Request.Builder request = new Request.Builder().url(configCenterUrl).post(formBody);
+
+        try (Response response = new OkHttpClient.Builder().build().newCall(request.build()).execute();) {
+        } catch (Exception e) {
+            // ignore
+            log.warn(e.getMessage());
+        }
+    }
+
+}

+ 88 - 0
config-center-client/src/main/java/cn/com/qmth/framework/config/center/client/utils/PropertyUtils.java

@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:25
+ */
+
+package cn.com.qmth.framework.config.center.client.utils;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+public class PropertyUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(PropertyUtils.class);
+
+    private static final String CHARSET_UTF_8 = "UTF-8";
+
+    /**
+     * 获取配置文件内容
+     */
+    public static Properties loadProperties(String... propertyPaths) {
+        if (ArrayUtils.isEmpty(propertyPaths)) {
+            throw new IllegalArgumentException("propertyPaths must be not empty.");
+        }
+
+        log.info("loadProperties, path --> {}", StringUtils.join(propertyPaths, ","));
+        Properties properties = new Properties();
+
+        for (String propertyPath : propertyPaths) {
+            InputStream is = PropertyUtils.class.getClassLoader().getResourceAsStream(propertyPath);
+            if (is == null) {
+                String msg = String.format("%s is not exist!", propertyPath);
+                throw new IllegalArgumentException(msg);
+            }
+
+            try (InputStreamReader isr = new InputStreamReader(is, CHARSET_UTF_8);
+                 BufferedReader br = new BufferedReader(isr);) {
+
+                Properties curProperties = new Properties();
+                curProperties.load(br);
+
+                // 合并配置项(后面的配置文件内容优先级高于前面的!!!)
+                properties.putAll(curProperties);
+            } catch (Exception e) {
+                log.error(e.getMessage());
+                throw new RuntimeException(e);
+            } finally {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // ignore...
+                }
+            }
+        }
+
+        return properties;
+    }
+
+    /**
+     * 获取配置文件内容
+     */
+    public static Map<String, String> loadMapProperties(String... propertyPaths) {
+        Properties properties = loadProperties(propertyPaths);
+        return toMapProperties(properties);
+    }
+
+    public static Map<String, String> toMapProperties(Properties properties) {
+        Map<String, String> props = new HashMap<>();
+
+        if (properties != null) {
+            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+                props.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
+            }
+        }
+
+        return props;
+    }
+
+}

+ 6 - 0
config-center-client/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,6 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+cn.com.qmth.framework.config.center.client.ConfigCenterClientAutoConfiguration
+org.springframework.boot.SpringApplicationRunListener=\
+cn.com.qmth.framework.config.center.client.core.CustomSpringApplicationRunListener
+org.springframework.boot.env.EnvironmentPostProcessor=\
+cn.com.qmth.framework.config.center.client.core.CustomEnvironmentPostProcessor

+ 82 - 0
config-center-server/pom.xml

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>cn.com.qmth.framework</groupId>
+    <artifactId>config-center-server</artifactId>
+    <packaging>jar</packaging>
+    <version>1.0.0</version>
+
+    <properties>
+        <spring-boot.version>2.3.10.RELEASE</spring-boot.version>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                    <skipTests>true</skipTests>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <!--<mainClass>x.x.x.Application</mainClass>-->
+                    <layout>ZIP</layout>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 18 - 0
config-center-server/src/main/java/cn/com/qmth/framework/config/center/ConfigCenterApplication.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:21
+ */
+
+package cn.com.qmth.framework.config.center;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ConfigCenterApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(ConfigCenterApplication.class, args);
+    }
+
+}

+ 55 - 0
config-center-server/src/main/java/cn/com/qmth/framework/config/center/controller/ConfigCenterController.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:21
+ */
+
+package cn.com.qmth.framework.config.center.controller;
+
+import cn.com.qmth.framework.config.center.utils.PropertyUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * @author: Deason
+ * @since: 2021/4/21
+ */
+@RestController
+@RequestMapping("/config/center")
+public class ConfigCenterController {
+
+    private static final Logger log = LoggerFactory.getLogger(ConfigCenterController.class);
+
+    /**
+     * 获取配置文件内容
+     *
+     * @param namespace 命名空间代码
+     * @param appCode   应用代码
+     * @param profile   环境(如:dev、test、prod)
+     * @return
+     */
+    @RequestMapping(value = "/pull/{namespace}/{appCode}/{profile}", method = {RequestMethod.GET, RequestMethod.POST})
+    public Map<String, String> pull(@PathVariable String namespace,
+                                    @PathVariable String appCode,
+                                    @PathVariable String profile) {
+        // 根配置文件路径规则:{namespace}/{profile}/application.properties
+        final String rootPropertyPath = String.format("%s/%s/application.properties", namespace, profile);
+
+        // 应用配置文件路径规则:{namespace}/{profile}/application-{appCode}.properties
+        final String appPropertyPath = String.format("%s/%s/application-%s.properties", namespace, profile, appCode);
+
+        // 合并配置项(后面的配置文件内容优先级高于前面的!!!)
+        return PropertyUtils.loadMapProperties(rootPropertyPath, appPropertyPath);
+    }
+
+    @RequestMapping(value = "/notice/{namespace}/{appCode}/{profile}", method = {RequestMethod.GET, RequestMethod.POST})
+    public void notice(@PathVariable String namespace,
+                       @PathVariable String appCode,
+                       @PathVariable String profile,
+                       @RequestParam(required = false) String msg) {
+        log.info("namespace = {}, appCode = {}, profile = {}, msg = {}", namespace, appCode, profile, msg);
+    }
+
+}

+ 19 - 0
config-center-server/src/main/java/cn/com/qmth/framework/config/center/controller/IndexController.java

@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:21
+ */
+
+package cn.com.qmth.framework.config.center.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class IndexController {
+
+    @GetMapping(value = "/")
+    public String index() {
+        return "ok";
+    }
+
+}

+ 38 - 0
config-center-server/src/main/java/cn/com/qmth/framework/config/center/core/ControllerAdviceHandler.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:21
+ */
+
+package cn.com.qmth.framework.config.center.core;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * @author: Deason
+ * @since: 2021/4/21
+ */
+@ResponseBody
+@ControllerAdvice
+public class ControllerAdviceHandler {
+
+    private final static Logger log = LoggerFactory.getLogger(ControllerAdviceHandler.class);
+
+    @ExceptionHandler(value = RuntimeException.class)
+    public ResponseEntity<String> handle(RuntimeException e) {
+        log.error(e.getMessage(), e);
+        return new ResponseEntity(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @ExceptionHandler(value = Exception.class)
+    public ResponseEntity<String> handle(Exception e) {
+        log.error(e.getMessage(), e);
+        return new ResponseEntity(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+}

+ 88 - 0
config-center-server/src/main/java/cn/com/qmth/framework/config/center/utils/PropertyUtils.java

@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2021 the original author, All Rights Reserved.
+ * Created by Deason on 2021-04-28 22:23:21
+ */
+
+package cn.com.qmth.framework.config.center.utils;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+public class PropertyUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(PropertyUtils.class);
+
+    private static final String CHARSET_UTF_8 = "UTF-8";
+
+    /**
+     * 获取配置文件内容
+     */
+    public static Properties loadProperties(String... propertyPaths) {
+        if (ArrayUtils.isEmpty(propertyPaths)) {
+            throw new IllegalArgumentException("propertyPaths must be not empty.");
+        }
+
+        log.info("loadProperties, path --> {}", StringUtils.join(propertyPaths, ","));
+        Properties properties = new Properties();
+
+        for (String propertyPath : propertyPaths) {
+            InputStream is = PropertyUtils.class.getClassLoader().getResourceAsStream(propertyPath);
+            if (is == null) {
+                String msg = String.format("%s is not exist!", propertyPath);
+                throw new IllegalArgumentException(msg);
+            }
+
+            try (InputStreamReader isr = new InputStreamReader(is, CHARSET_UTF_8);
+                 BufferedReader br = new BufferedReader(isr);) {
+
+                Properties curProperties = new Properties();
+                curProperties.load(br);
+
+                // 合并配置项(后面的配置文件内容优先级高于前面的!!!)
+                properties.putAll(curProperties);
+            } catch (Exception e) {
+                log.error(e.getMessage());
+                throw new RuntimeException(e);
+            } finally {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // ignore...
+                }
+            }
+        }
+
+        return properties;
+    }
+
+    /**
+     * 获取配置文件内容
+     */
+    public static Map<String, String> loadMapProperties(String... propertyPaths) {
+        Properties properties = loadProperties(propertyPaths);
+        return toMapProperties(properties);
+    }
+
+    public static Map<String, String> toMapProperties(Properties properties) {
+        Map<String, String> props = new HashMap<>();
+
+        if (properties != null) {
+            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+                props.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
+            }
+        }
+
+        return props;
+    }
+
+}

+ 8 - 0
config-center-server/src/main/resources/application.properties

@@ -0,0 +1,8 @@
+# server config
+server.port=8888
+server.servlet.context-path=/
+server.tomcat.uri-encoding=UTF-8
+spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
+spring.jackson.time-zone=GMT+8
+# logging config
+sys.log.level=debug

+ 5 - 0
config-center-server/src/main/resources/examcloud/dev/application-demo.properties

@@ -0,0 +1,5 @@
+# ...
+sys.log.level=info
+test1=11111
+test2=22222
+test3=

+ 5 - 0
config-center-server/src/main/resources/examcloud/dev/application.properties

@@ -0,0 +1,5 @@
+# ...
+sys.log.level=error
+test1=111
+test2=222
+test3=333

+ 5 - 0
config-center-server/src/main/resources/examcloud/prod/application-demo.properties

@@ -0,0 +1,5 @@
+# ...
+sys.log.level=info
+test1=aaaaa
+test2=bbbbb
+test3=

+ 5 - 0
config-center-server/src/main/resources/examcloud/prod/application.properties

@@ -0,0 +1,5 @@
+# ...
+sys.log.level=error
+test1=111
+test2=222
+test3=333

+ 46 - 0
config-center-server/src/main/resources/logback-spring.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration debug="false" scan="true" scanPeriod="30 seconds">
+
+    <springProperty name="LOG_LEVEL" source="sys.log.level" scope="context" defaultValue="INFO"/>
+
+    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
+    <property name="LOG_PATTERN"
+              value="%d{yyyy-MM-dd HH:mm:ss.SSS} | %clr(%-5level) | %clr(%logger{39}:%L){cyan} | %clr(%thread %X{TRACE_ID}){blue} | %msg%n"/>
+
+    <appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <appender name="FILE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+
+        <file>logs/config-center.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>logs/config-center-%d{yyyyMMdd}-%i.log</fileNamePattern>
+            <maxHistory>30</maxHistory>
+            <maxFileSize>100MB</maxFileSize>
+            <totalSizeCap>10GB</totalSizeCap>
+        </rollingPolicy>
+    </appender>
+
+    <logger name="org.springframework" level="WARN"/>
+    <logger name="org.hibernate" level="WARN"/>
+    <logger name="org.apache" level="WARN"/>
+
+    <Logger name="cn.com.qmth" level="${LOG_LEVEL}" additivity="false">
+        <appender-ref ref="CONSOLE_APPENDER"/>
+        <appender-ref ref="FILE_APPENDER"/>
+    </Logger>
+
+    <root level="${LOG_LEVEL}">
+        <appender-ref ref="CONSOLE_APPENDER"/>
+        <appender-ref ref="FILE_APPENDER"/>
+    </root>
+
+</configuration>

+ 15 - 0
pom.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>cn.com.qmth.framework</groupId>
+    <artifactId>config-center</artifactId>
+    <packaging>pom</packaging>
+    <version>1.0.0</version>
+
+    <modules>
+        <module>config-center-server</module>
+        <module>config-center-client</module>
+    </modules>
+
+</project>