getErrorAttributes(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
+ return errorAttributes.getErrorAttributes((WebRequest) attributes, isIncludeStackTrace(errorProperties));
+ }
+
+ /**
+ * Determine if the stacktrace attribute should be included.
+ *
+ * @return if the stacktrace attribute should be included
+ */
+ private boolean isIncludeStackTrace(ErrorProperties errorProperties) {
+ ErrorProperties.IncludeStacktrace include = errorProperties.getIncludeStacktrace();
+ return include == ErrorProperties.IncludeStacktrace.ALWAYS
+ || include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM
+ && getTraceParameter();
+ }
+
+ private boolean getTraceParameter() {
+ Object parameter = request.getParameter("trace");
+ return parameter != null && !"false".equals(parameter.toString().toLowerCase());
+ }
+ }
+}
diff --git a/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/AxzoProperties.java b/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/AxzoProperties.java
new file mode 100644
index 0000000..52d9b66
--- /dev/null
+++ b/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/AxzoProperties.java
@@ -0,0 +1,66 @@
+package cn.axzo.framework.autoconfigure.web.swagger;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/10 11:53
+ **/
+@Validated
+@ConfigurationProperties("axzo")
+@Data
+public class AxzoProperties {
+
+ private Swagger swagger;
+
+ private YApi yApi;
+
+ @Data
+ public static class Swagger {
+ private boolean enabled = false;
+ private String basePackage = "cn";
+ private String title = "Application API";
+ private String description = "API documentation";
+ private String version = "1.0.0";
+ private String termsOfServiceUrl = null;
+ private String contactName = null;
+ private String contactUrl = null;
+ private String contactEmail = null;
+ private String license = null;
+ private String licenseUrl = null;
+ private String defaultIncludePattern = "/api/.*";
+ private String host = null;
+ private String[] protocols = new String[0];
+ }
+
+ @Data
+ public static class YApi {
+ /**
+ * 是否同步YApi
+ */
+ public boolean enabled = false;
+
+ /**
+ * 数据同步方式 normal"(普通模式) , "good"(智能合并), "merge"(完全覆盖) 三种模式
+ */
+ private String merge = "normal";
+
+ /**
+ * 对应文件夹的 token
+ */
+ private String token;
+
+ /**
+ * json 数据来源(代替 json 字符串)。
+ */
+ private String url;
+
+ /**
+ * 组名称,一般 默认 default
+ */
+ private String groupName = "default";
+ }
+}
diff --git a/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/SwaggerAutoConfiguration.java b/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/SwaggerAutoConfiguration.java
new file mode 100644
index 0000000..054860c
--- /dev/null
+++ b/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/SwaggerAutoConfiguration.java
@@ -0,0 +1,216 @@
+package cn.axzo.framework.autoconfigure.web.swagger;
+
+import cn.axzo.framework.autoconfigure.web.swagger.customizer.BuildInSwaggerCustomizer;
+import cn.axzo.framework.autoconfigure.web.swagger.customizer.SecuritySwaggerCustomizer;
+import cn.axzo.framework.autoconfigure.web.swagger.customizer.SwaggerCustomizer;
+import cn.axzo.framework.domain.web.result.ApiListResult;
+import cn.axzo.framework.domain.web.result.ApiPageResult;
+import cn.axzo.framework.domain.web.result.ApiResult;
+import cn.axzo.framework.context.Placeholders;
+import cn.axzo.framework.web.http.ApiEntity;
+import cn.axzo.framework.web.http.ApiListEntity;
+import cn.axzo.framework.web.http.ApiPageEntity;
+import com.fasterxml.classmate.TypeResolver;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.*;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Profile;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.StopWatch;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.DispatcherServlet;
+import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
+import springfox.documentation.schema.AlternateTypeRule;
+import springfox.documentation.schema.AlternateTypeRules;
+import springfox.documentation.schema.WildcardType;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger.web.UiConfiguration;
+import springfox.documentation.swagger.web.UiConfigurationBuilder;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+import javax.servlet.Servlet;
+import java.time.LocalDate;
+import java.util.*;
+import java.util.concurrent.Callable;
+
+import static cn.axzo.framework.core.Constants.*;
+import static springfox.documentation.builders.PathSelectors.regex;
+import static springfox.documentation.schema.AlternateTypeRules.newRule;
+
+/**
+ * Springfox Swagger configuration.
+ *
+ * Warning! When having a lot of REST endpoints, Springfox can become a performance issue. In that case, you can use a
+ * specific Spring profile for this class, so that only front-end developers have access to the Swagger view.
+ */
+@Slf4j
+@Configuration
+@ConditionalOnWebApplication
+@ConditionalOnClass({
+ ApiInfo.class,
+ BeanValidatorPluginsConfiguration.class,
+ Servlet.class,
+ DispatcherServlet.class,
+ AxzoProperties.class
+})
+@ConditionalOnProperty(value = "axzo.swagger.enabled", havingValue = "true")
+@EnableConfigurationProperties(AxzoProperties.class)
+@Profile({ENV_LOCAL, ENV_DEV, ENV_TEST, ENV_UAT, ENV_FAT})
+@EnableSwagger2
+@Import(BeanValidatorPluginsConfiguration.class)
+@RequiredArgsConstructor
+public class SwaggerAutoConfiguration {
+
+ public static final String STARTING_MESSAGE = "Starting Swagger";
+ public static final String STARTED_MESSAGE = "Started Swagger in {} ms";
+ public static final String MANAGEMENT_TITLE_SUFFIX = "Management API";
+ public static final String MANAGEMENT_GROUP_NAME = "management";
+ public static final String MANAGEMENT_DESCRIPTION = "Management endpoints documentation";
+
+ private final AxzoProperties properties;
+
+ /**
+ * Springfox configuration for the API Swagger docs.
+ *
+ * @return the Swagger Springfox configuration
+ */
+ @Bean
+ @ConditionalOnMissingBean(name = "swaggerApiDocket")
+ public Docket swaggerApiDocket(List swaggerCustomizers,
+ ObjectProvider alternateTypeRulesProviders) {
+ log.debug(STARTING_MESSAGE);
+ StopWatch watch = new StopWatch();
+ watch.start();
+
+ Docket docket = createDocket();
+
+ // Apply all customizers
+ swaggerCustomizers.forEach(customizer -> customizer.customize(docket));
+
+ // Add AlternateTypeRules if available in spring bean factory.
+ // Also you can add them in your customizer bean.
+ Optional.ofNullable(alternateTypeRulesProviders.getIfAvailable()).ifPresent(docket::alternateTypeRules);
+
+ watch.stop();
+ log.debug(STARTED_MESSAGE, watch.getTotalTimeMillis());
+ return docket;
+ }
+
+ @Bean
+ public BuildInSwaggerCustomizer buildInSwaggerCustomizer() {
+ return new BuildInSwaggerCustomizer(properties.getSwagger());
+ }
+
+ @ConditionalOnClass(name = "org.springframework.security.core.annotation.AuthenticationPrincipal")
+ @Bean
+ public SecuritySwaggerCustomizer securitySwaggerCustomizer() {
+ return new SecuritySwaggerCustomizer();
+ }
+
+ @Bean
+ public AlternateTypeRule localDateListAlternateTypeRule(TypeResolver typeResolver) {
+ return newRule(
+ typeResolver.resolve(List.class, LocalDate.class),
+ typeResolver.resolve(List.class, String.class)
+ );
+ }
+
+ @ConditionalOnClass(name = "cn.axzo.framework.web.http.ApiEntity")
+ @Bean
+ public AlternateTypeRule apiEntityAlternateTypeRule(TypeResolver typeResolver) {
+ return AlternateTypeRules.newRule(
+ typeResolver.resolve(ApiEntity.class, WildcardType.class),
+ typeResolver.resolve(ApiResult.class, WildcardType.class)
+ );
+ }
+
+ @ConditionalOnClass(name = "cn.axzo.framework.web.http.ApiEntity")
+ @Bean
+ public AlternateTypeRule callableApiEntityAlternateTypeRule(TypeResolver typeResolver) {
+ return newRule(
+ typeResolver.resolve(Callable.class, typeResolver.resolve(ApiEntity.class, WildcardType.class)),
+ typeResolver.resolve(ApiResult.class, WildcardType.class)
+ );
+ }
+
+ @ConditionalOnClass(name = "cn.axzo.framework.web.http.ApiListEntity")
+ @Bean
+ public AlternateTypeRule apiListEntityAlternateTypeRule(TypeResolver typeResolver) {
+ return newRule(typeResolver.resolve(ApiListEntity.class, WildcardType.class),
+ typeResolver.resolve(ApiListResult.class, WildcardType.class));
+ }
+
+ @ConditionalOnClass(name = "cn.axzo.framework.web.http.ApiPageEntity")
+ @Bean
+ public AlternateTypeRule apiPageEntityAlternateTypeRule(TypeResolver typeResolver) {
+ return newRule(typeResolver.resolve(ApiPageEntity.class, WildcardType.class),
+ typeResolver.resolve(ApiPageResult.class, WildcardType.class));
+ }
+
+ @Bean
+ public AlternateTypeRule requestEntityAlternateTypeRule(TypeResolver typeResolver) {
+ return AlternateTypeRules.newRule(
+ typeResolver.resolve(RequestEntity.class, WildcardType.class),
+ typeResolver.resolve(WildcardType.class)
+ );
+ }
+
+ /**
+ * Springfox configuration for the management endpoints (actuator) Swagger docs.
+ *
+ * @return the Swagger Springfox configuration
+ */
+ @Bean
+ @ConditionalOnClass(name = "org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties")
+ @ConditionalOnProperty("management.server.servlet.context-path")
+ @ConditionalOnExpression("'${management.server.servlet.context-path}'.length() > 0")
+ @ConditionalOnMissingBean(name = "swaggerManagementDocket")
+ public Docket swaggerManagementDocket(@Value(Placeholders.APPLICATION_NAME) String appName,
+ @Value("${management.server.servlet.context-path}") String managementContextPath) {
+ ApiInfo apiInfo = new ApiInfo(
+ StringUtils.capitalize(appName) + " " + MANAGEMENT_TITLE_SUFFIX,
+ MANAGEMENT_DESCRIPTION,
+ properties.getSwagger().getVersion(),
+ "",
+ ApiInfo.DEFAULT_CONTACT,
+ "",
+ "",
+ new ArrayList<>()
+ );
+
+ return createDocket()
+ .apiInfo(apiInfo)
+ .groupName(MANAGEMENT_GROUP_NAME)
+ .host(properties.getSwagger().getHost())
+ .protocols(new HashSet<>(Arrays.asList(properties.getSwagger().getProtocols())))
+ .forCodeGeneration(true)
+ .directModelSubstitute(java.nio.ByteBuffer.class, String.class)
+ .genericModelSubstitutes(ResponseEntity.class)
+ .select()
+ .paths(regex(managementContextPath + ".*"))
+ .build();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public UiConfiguration uiConfiguration() {
+ return UiConfigurationBuilder.builder()
+ .defaultModelsExpandDepth(-1)
+ .displayRequestDuration(true)
+ .validatorUrl("")
+ .build();
+ }
+
+ protected Docket createDocket() {
+ return new Docket(DocumentationType.SWAGGER_2);
+ }
+}
diff --git a/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/customizer/BuildInSwaggerCustomizer.java b/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/customizer/BuildInSwaggerCustomizer.java
new file mode 100644
index 0000000..b7930df
--- /dev/null
+++ b/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/customizer/BuildInSwaggerCustomizer.java
@@ -0,0 +1,96 @@
+package cn.axzo.framework.autoconfigure.web.swagger.customizer;
+
+import cn.axzo.framework.autoconfigure.web.swagger.AxzoProperties;
+import com.fasterxml.classmate.TypeResolver;
+import org.apache.commons.math3.fraction.BigFraction;
+import org.apache.commons.math3.fraction.Fraction;
+import org.springframework.core.Ordered;
+import org.springframework.http.RequestEntity;
+import springfox.documentation.schema.AlternateTypeRule;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spring.web.plugins.Docket;
+
+import java.time.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import static springfox.documentation.builders.PathSelectors.regex;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/10 12:00
+ **/
+public class BuildInSwaggerCustomizer implements SwaggerCustomizer, Ordered {
+
+ /**
+ * The default order for the customizer.
+ */
+ public static final int DEFAULT_ORDER = 0;
+
+ private int order = DEFAULT_ORDER;
+
+ private final AlternateTypeRule[] rules;
+
+ private final AxzoProperties.Swagger properties;
+
+ public BuildInSwaggerCustomizer(AxzoProperties.Swagger properties, AlternateTypeRule... rules) {
+ this.properties = properties;
+ this.rules = rules;
+ }
+
+ @Override
+ public void customize(Docket docket) {
+ Contact contact = new Contact(
+ properties.getContactName(),
+ properties.getContactUrl(),
+ properties.getContactEmail()
+ );
+
+ ApiInfo apiInfo = new ApiInfo(
+ properties.getTitle(),
+ properties.getDescription(),
+ properties.getVersion(),
+ properties.getTermsOfServiceUrl(),
+ contact,
+ properties.getLicense(),
+ properties.getLicenseUrl(),
+ new ArrayList<>()
+ );
+
+ docket.select()
+ .paths(regex(properties.getDefaultIncludePattern()))
+ .build()
+
+ .host(properties.getHost())
+ .protocols(new HashSet<>(Arrays.asList(properties.getProtocols())))
+ .apiInfo(apiInfo)
+ .forCodeGeneration(true)
+ .ignoredParameterTypes(RequestEntity.class)
+ .additionalModels(new TypeResolver().resolve(List.class))
+ .directModelSubstitute(ZonedDateTime.class, Long.class)
+ .directModelSubstitute(Instant.class, Long.class)
+ .directModelSubstitute(OffsetDateTime.class, Long.class)
+ .directModelSubstitute(LocalTime.class, String.class)
+ .directModelSubstitute(LocalDate.class, String.class)
+ .directModelSubstitute(LocalDateTime.class, String.class)
+ .directModelSubstitute(YearMonth.class, String.class)
+ .directModelSubstitute(BigFraction.class, String.class)
+ .directModelSubstitute(Fraction.class, String.class)
+ .directModelSubstitute(Period.class, String.class)
+ .directModelSubstitute(java.nio.ByteBuffer.class, String.class)
+ .alternateTypeRules(rules);
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ @Override
+ public int getOrder() {
+ return order;
+ }
+}
diff --git a/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/customizer/SecuritySwaggerCustomizer.java b/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/customizer/SecuritySwaggerCustomizer.java
new file mode 100644
index 0000000..ed00631
--- /dev/null
+++ b/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/customizer/SecuritySwaggerCustomizer.java
@@ -0,0 +1,17 @@
+package cn.axzo.framework.autoconfigure.web.swagger.customizer;
+
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import springfox.documentation.spring.web.plugins.Docket;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/10 12:00
+ **/
+public class SecuritySwaggerCustomizer implements SwaggerCustomizer {
+
+ @Override
+ public void customize(Docket docket) {
+ docket.ignoredParameterTypes(AuthenticationPrincipal.class);
+ }
+}
diff --git a/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/customizer/SwaggerCustomizer.java b/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/customizer/SwaggerCustomizer.java
new file mode 100644
index 0000000..74157d7
--- /dev/null
+++ b/axzo-common-autoconfigure/src/main/java/cn.axzo.framework.autoconfigure/web/swagger/customizer/SwaggerCustomizer.java
@@ -0,0 +1,14 @@
+package cn.axzo.framework.autoconfigure.web.swagger.customizer;
+
+import springfox.documentation.spring.web.plugins.Docket;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/10 11:59
+ **/
+@FunctionalInterface
+public interface SwaggerCustomizer {
+
+ void customize(Docket docket);
+}
diff --git a/axzo-common-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/axzo-common-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
new file mode 100644
index 0000000..f29fc45
--- /dev/null
+++ b/axzo-common-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -0,0 +1,62 @@
+{
+ "groups": [
+ ],
+ "properties": [
+ {
+ "name": "id.generator.node-id",
+ "type": "java.lang.Integer"
+ },
+ {
+ "name": "id.generator.base-timestamp",
+ "type": "java.lang.Long"
+ },
+ {
+ "name": "id.generator.sequence-bits",
+ "type": "java.lang.Integer"
+ },
+ {
+ "name": "spring.mvc.http-log.enabled",
+ "type": "java.lang.Boolean",
+ "defaultValue": false
+ },
+ {
+ "name": "spring.application.log-ready-info",
+ "type": "java.lang.Boolean",
+ "defaultValue": true
+ },
+ {
+ "name": "uaa.enabled",
+ "type": "java.lang.Boolean",
+ "defaultValue": true
+ },
+ {
+ "name": "spring.mvc.enable-matrix-variables",
+ "type": "java.lang.Boolean",
+ "defaultValue": true
+ },
+ {
+ "name": "spring.cloud.config.server.health.enabled",
+ "type": "java.lang.Boolean",
+ "defaultValue": true
+ },
+ {
+ "name": "spring.cloud.config.server.health.token",
+ "type": "java.lang.String"
+ },
+ {
+ "name": "spring.cloud.config.server.consul.watch.enabled",
+ "type": "java.lang.Boolean",
+ "defaultValue": false
+ },
+ {
+ "name": "spring.mvc.response.verbose-enabled",
+ "type": "java.lang.Boolean",
+ "defaultValue": false
+ },
+ {
+ "name": "spring.mvc.response.dynamic-enabled",
+ "type": "java.lang.Boolean",
+ "defaultValue": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/axzo-common-autoconfigure/src/main/resources/META-INF/spring.factories b/axzo-common-autoconfigure/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..ba1b083
--- /dev/null
+++ b/axzo-common-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,18 @@
+# Environment Post Processors
+org.springframework.boot.env.EnvironmentPostProcessor=\
+cn.axzo.framework.autoconfigure.env.BuildInConfigOverridePostProcessor
+
+# Auto Configuration
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+cn.axzo.framework.autoconfigure.env.ApplicationReadyInfoAutoConfiguration,\
+cn.axzo.framework.autoconfigure.validation.MethodValidationAutoConfiguration,\
+cn.axzo.framework.autoconfigure.jackson.JacksonModuleAutoConfiguration,\
+cn.axzo.framework.autoconfigure.data.IdAutoConfiguration,\
+cn.axzo.framework.autoconfigure.web.cors.CorsAutoConfiguration,\
+cn.axzo.framework.autoconfigure.web.PageWebAutoConfiguration,\
+cn.axzo.framework.autoconfigure.web.exception.ExceptionHandlerAutoConfiguration,\
+# cn.axzo.framework.autoconfigure.web.swagger.SwaggerAutoConfiguration,\
+cn.axzo.framework.autoconfigure.validation.SpringValidatorAutoConfiguration,\
+cn.axzo.framework.autoconfigure.web.advice.BodyAdviceAutoConfiguration,\
+cn.axzo.framework.autoconfigure.web.FilterAutoConfiguration,\
+cn.axzo.framework.autoconfigure.web.context.WebMvcAwareAutoConfiguration
diff --git a/axzo-common-boot/pom.xml b/axzo-common-boot/pom.xml
new file mode 100644
index 0000000..e67695e
--- /dev/null
+++ b/axzo-common-boot/pom.xml
@@ -0,0 +1,85 @@
+
+
+ 4.0.0
+
+
+ axzo-framework-commons
+ cn.axzo.framework
+ 1.0.0-SNAPSHOT
+
+
+ axzo-common-boot
+ Axzo Common Boot
+
+
+ cn/axzo/framework/boot
+
+
+
+
+
+ cn.axzo.framework.framework
+ axzo-common-context
+
+
+ org.springframework.boot
+ spring-boot
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+ org.apache.logging.log4j
+ log4j-api
+ true
+
+
+
+
+
+
+ com.google.code.maven-replacer-plugin
+ replacer
+
+
+
+ process-packageVersion
+
+ replace
+
+ generate-sources
+
+
+
+ ${basedir}/src/main/java/${pkgVersion.dir}/PackageVersion.java.in
+ ${project.basedir}/src/main/java/${pkgVersion.dir}/PackageVersion.java
+
+
+ @package@
+ ${project.groupId}.boot
+
+
+ @projectversion@
+ ${project.version}
+
+
+ @spring_cloud_version@
+ ${spring-cloud.version}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/DefaultProfileOverrideListener.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/DefaultProfileOverrideListener.java
new file mode 100644
index 0000000..4800bbe
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/DefaultProfileOverrideListener.java
@@ -0,0 +1,17 @@
+package cn.axzo.framework.boot;
+
+import org.springframework.boot.context.event.ApplicationStartingEvent;
+import org.springframework.context.ApplicationListener;
+
+/**
+ * @Descriptiono
+ * @Author liyong.tian
+ * @Date 2020/9/8 19:07
+ **/
+public class DefaultProfileOverrideListener implements ApplicationListener {
+
+ @Override
+ public void onApplicationEvent(ApplicationStartingEvent event) {
+ DefaultProfileUtil.setDefaultProfile(event.getSpringApplication());
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/DefaultProfileUtil.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/DefaultProfileUtil.java
new file mode 100644
index 0000000..62d3129
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/DefaultProfileUtil.java
@@ -0,0 +1,58 @@
+package cn.axzo.framework.boot;
+
+import cn.axzo.framework.core.Constants;
+import jodd.util.ArraysUtil;
+import org.springframework.boot.SpringApplication;
+import org.springframework.core.env.Environment;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.collect.Sets.newHashSet;
+
+/**
+ * Utility class to load a Spring profile to be used as default
+ * when there is no spring.profiles.active set in the environment or as command line argument.
+ * If the value is not available in application.yml then local profile will be used as default.
+ */
+public final class DefaultProfileUtil {
+
+ private static final String SPRING_PROFILE_DEFAULT = "spring.profiles.default";
+
+ private DefaultProfileUtil() {
+ }
+
+ /**
+ * Set a default to use when no profile is configured.
+ *
+ * @param app the Spring application
+ */
+ public static void setDefaultProfile(SpringApplication app) {
+ Map defProperties = new HashMap<>();
+ /*
+ * The default profile to use when no other profiles are defined
+ * This cannot be set in the application.yml file.
+ * See https://github.com/spring-projects/spring-boot/issues/1219
+ */
+ defProperties.put(SPRING_PROFILE_DEFAULT, Constants.ENV_LOCAL);
+ app.setDefaultProperties(defProperties);
+ }
+
+ /**
+ * Get the profiles that are applied else get default profiles.
+ *
+ * @param env spring environment
+ * @return profiles
+ */
+ public static String[] getActiveProfiles(Environment env) {
+ String[] profiles = env.getActiveProfiles();
+ if (profiles.length == 0) {
+ return env.getDefaultProfiles();
+ }
+ if (Arrays.stream(profiles).noneMatch(profile -> ArraysUtil.contains(Constants.ENV_ALL, profile))) {
+ return newHashSet(ArraysUtil.join(profiles, env.getDefaultProfiles())).toArray(new String[0]);
+ }
+ return profiles;
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/DynamicBannerEnvironmentPostProcessor.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/DynamicBannerEnvironmentPostProcessor.java
new file mode 100644
index 0000000..f3df5a9
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/DynamicBannerEnvironmentPostProcessor.java
@@ -0,0 +1,40 @@
+package cn.axzo.framework.boot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static cn.axzo.framework.boot.PackageVersion.VERSION;
+import static cn.axzo.framework.boot.PackageVersion.SPRING_CLOUD_VERSION;
+import static cn.axzo.framework.core.util.ClassUtil.isPresent;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/8 19:14
+ **/
+public class DynamicBannerEnvironmentPostProcessor implements EnvironmentPostProcessor {
+
+ @Override
+ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
+ ClassLoader classLoader = application.getClassLoader();
+ boolean isSpringCloudPresent = isPresent("org.springframework.cloud.client.SpringCloudApplication", classLoader);
+
+ Map versionsMap = new HashMap<>();
+ versionsMap.put("axzo-framework.formatted-version", " (v" + VERSION + ")");
+
+ versionsMap.put("spring-cloud.version", isSpringCloudPresent ? SPRING_CLOUD_VERSION : null);
+
+ StringBuilder runningDesc = new StringBuilder();
+ if (isSpringCloudPresent) {
+ runningDesc.append("Spring Cloud ${spring-cloud.version} : ");
+ }
+ versionsMap.put("running.description", runningDesc.append("Spring Boot ${spring-boot.version}").toString());
+
+ environment.getPropertySources().addLast(new MapPropertySource("version", versionsMap));
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/EnvironmentUtil.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/EnvironmentUtil.java
new file mode 100644
index 0000000..6a48ee5
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/EnvironmentUtil.java
@@ -0,0 +1,37 @@
+package cn.axzo.framework.boot;
+
+import cn.axzo.framework.core.FetchException;
+import cn.axzo.framework.core.net.Inets;
+import lombok.experimental.UtilityClass;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
+
+import java.util.Optional;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/8 19:11
+ **/
+@UtilityClass
+public class EnvironmentUtil {
+
+ private static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";
+
+ public boolean isSpringCloudContext(ConfigurableEnvironment environment) {
+ return environment != null && environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME);
+ }
+
+ public int fetchLocalIpTimeoutSeconds(Environment env) {
+ return env.getProperty("system.properties.fetch-local-ip-timeout-seconds", Integer.class, 2);
+ }
+
+ public Optional fetchLocalIp(Environment env) {
+ int timeoutSeconds = fetchLocalIpTimeoutSeconds(env);
+ try {
+ return Optional.ofNullable(Inets.fetchLocalIp(timeoutSeconds));
+ } catch (FetchException e) {
+ return Optional.empty();
+ }
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/PackageVersion.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/PackageVersion.java
new file mode 100644
index 0000000..d5855e5
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/PackageVersion.java
@@ -0,0 +1,13 @@
+package cn.axzo.framework.boot;
+
+/**
+ * Automatically generated from PackageVersion.java.in during
+ * packageVersion-generate execution of maven-replacer-plugin in
+ * pom.xml.
+ */
+public final class PackageVersion {
+
+ public final static String VERSION = "1.0.0-SNAPSHOT";
+
+ public final static String SPRING_CLOUD_VERSION = "2020.0.6";
+}
\ No newline at end of file
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/PackageVersion.java.in b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/PackageVersion.java.in
new file mode 100644
index 0000000..847ed45
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/PackageVersion.java.in
@@ -0,0 +1,13 @@
+package @package@;
+
+/**
+ * Automatically generated from PackageVersion.java.in during
+ * packageVersion-generate execution of maven-replacer-plugin in
+ * pom.xml.
+ */
+public final class PackageVersion {
+
+ public final static String VERSION = "@projectversion@";
+
+ public final static String SPRING_CLOUD_VERSION = "@spring_cloud_version@";
+}
\ No newline at end of file
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/PropertySourceUtils.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/PropertySourceUtils.java
new file mode 100644
index 0000000..b14ab11
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/PropertySourceUtils.java
@@ -0,0 +1,68 @@
+package cn.axzo.framework.boot;
+
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.env.PropertySources;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/12/15 11:37
+ **/
+public abstract class PropertySourceUtils {
+
+ /**
+ * Return a Map of all values from the specified {@link PropertySources} that start
+ * with a particular key.
+ * @param propertySources the property sources to scan
+ * @param keyPrefix the key prefixes to test
+ * @return a map of all sub properties starting with the specified key prefixes.
+ * @see PropertySourceUtils#getSubProperties(PropertySources, String, String)
+ */
+ public static Map getSubProperties(PropertySources propertySources, String keyPrefix) {
+ return PropertySourceUtils.getSubProperties(propertySources, null, keyPrefix);
+ }
+
+ /**
+ * Return a Map of all values from the specified {@link PropertySources} that start
+ * with a particular key.
+ * @param propertySources the property sources to scan
+ * @param rootPrefix a root prefix to be prepended to the keyPrefix (can be
+ * {@code null})
+ * @param keyPrefix the key prefixes to test
+ * @return a map of all sub properties starting with the specified key prefixes.
+ * @see #getSubProperties(PropertySources, String, String)
+ */
+ public static Map getSubProperties(PropertySources propertySources, String rootPrefix,
+ String keyPrefix) {
+ RelaxedNames keyPrefixes = new RelaxedNames(keyPrefix);
+ Map subProperties = new LinkedHashMap();
+ for (PropertySource> source : propertySources) {
+ if (source instanceof EnumerablePropertySource) {
+ for (String name : ((EnumerablePropertySource>) source).getPropertyNames()) {
+ String key = PropertySourceUtils.getSubKey(name, rootPrefix, keyPrefixes);
+ if (key != null && !subProperties.containsKey(key)) {
+ subProperties.put(key, source.getProperty(name));
+ }
+ }
+ }
+ }
+ return Collections.unmodifiableMap(subProperties);
+ }
+
+ private static String getSubKey(String name, String rootPrefixes, RelaxedNames keyPrefix) {
+ rootPrefixes = (rootPrefixes != null) ? rootPrefixes : "";
+ for (String rootPrefix : new RelaxedNames(rootPrefixes)) {
+ for (String candidateKeyPrefix : keyPrefix) {
+ if (name.startsWith(rootPrefix + candidateKeyPrefix)) {
+ return name.substring((rootPrefix + candidateKeyPrefix).length());
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/RelaxedNames.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/RelaxedNames.java
new file mode 100644
index 0000000..0bd1b8d
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/RelaxedNames.java
@@ -0,0 +1,236 @@
+package cn.axzo.framework.boot;
+
+import org.springframework.util.StringUtils;
+
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/12/15 11:40
+ **/
+public final class RelaxedNames implements Iterable {
+
+ private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])");
+
+ private static final Pattern SEPARATED_TO_CAMEL_CASE_PATTERN = Pattern.compile("[_\\-.]");
+
+ private final String name;
+
+ private final Set values = new LinkedHashSet();
+
+ /**
+ * Create a new {@link RelaxedNames} instance.
+ * @param name the source name. For the maximum number of variations specify the name
+ * using dashed notation (e.g. {@literal my-property-name}
+ */
+ public RelaxedNames(String name) {
+ this.name = (name != null) ? name : "";
+ initialize(RelaxedNames.this.name, this.values);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return this.values.iterator();
+ }
+
+ private void initialize(String name, Set values) {
+ if (values.contains(name)) {
+ return;
+ }
+ for (Variation variation : Variation.values()) {
+ for (Manipulation manipulation : Manipulation.values()) {
+ String result = name;
+ result = manipulation.apply(result);
+ result = variation.apply(result);
+ values.add(result);
+ initialize(result, values);
+ }
+ }
+ }
+
+ /**
+ * Name variations.
+ */
+ enum Variation {
+
+ NONE {
+
+ @Override
+ public String apply(String value) {
+ return value;
+ }
+
+ },
+
+ LOWERCASE {
+
+ @Override
+ public String apply(String value) {
+ return (value.isEmpty() ? value : value.toLowerCase(Locale.ENGLISH));
+ }
+
+ },
+
+ UPPERCASE {
+
+ @Override
+ public String apply(String value) {
+ return (value.isEmpty() ? value : value.toUpperCase(Locale.ENGLISH));
+ }
+
+ };
+
+ public abstract String apply(String value);
+
+ }
+
+ /**
+ * Name manipulations.
+ */
+ enum Manipulation {
+
+ NONE {
+
+ @Override
+ public String apply(String value) {
+ return value;
+ }
+
+ },
+
+ HYPHEN_TO_UNDERSCORE {
+
+ @Override
+ public String apply(String value) {
+ return (value.indexOf('-') != -1) ? value.replace('-', '_') : value;
+ }
+
+ },
+
+ UNDERSCORE_TO_PERIOD {
+
+ @Override
+ public String apply(String value) {
+ return (value.indexOf('_') != -1) ? value.replace('_', '.') : value;
+ }
+
+ },
+
+ PERIOD_TO_UNDERSCORE {
+
+ @Override
+ public String apply(String value) {
+ return (value.indexOf('.') != -1) ? value.replace('.', '_') : value;
+ }
+
+ },
+
+ CAMELCASE_TO_UNDERSCORE {
+
+ @Override
+ public String apply(String value) {
+ if (value.isEmpty()) {
+ return value;
+ }
+ Matcher matcher = CAMEL_CASE_PATTERN.matcher(value);
+ if (!matcher.find()) {
+ return value;
+ }
+ matcher = matcher.reset();
+ StringBuffer result = new StringBuffer();
+ while (matcher.find()) {
+ matcher.appendReplacement(result,
+ matcher.group(1) + '_' + StringUtils.uncapitalize(matcher.group(2)));
+ }
+ matcher.appendTail(result);
+ return result.toString();
+ }
+
+ },
+
+ CAMELCASE_TO_HYPHEN {
+
+ @Override
+ public String apply(String value) {
+ if (value.isEmpty()) {
+ return value;
+ }
+ Matcher matcher = CAMEL_CASE_PATTERN.matcher(value);
+ if (!matcher.find()) {
+ return value;
+ }
+ matcher = matcher.reset();
+ StringBuffer result = new StringBuffer();
+ while (matcher.find()) {
+ matcher.appendReplacement(result,
+ matcher.group(1) + '-' + StringUtils.uncapitalize(matcher.group(2)));
+ }
+ matcher.appendTail(result);
+ return result.toString();
+ }
+
+ },
+
+ SEPARATED_TO_CAMELCASE {
+
+ @Override
+ public String apply(String value) {
+ return separatedToCamelCase(value, false);
+ }
+
+ },
+
+ CASE_INSENSITIVE_SEPARATED_TO_CAMELCASE {
+
+ @Override
+ public String apply(String value) {
+ return separatedToCamelCase(value, true);
+ }
+
+ };
+
+ private static final char[] SUFFIXES = new char[] { '_', '-', '.' };
+
+ public abstract String apply(String value);
+
+ private static String separatedToCamelCase(String value, boolean caseInsensitive) {
+ if (value.isEmpty()) {
+ return value;
+ }
+ StringBuilder builder = new StringBuilder();
+ for (String field : SEPARATED_TO_CAMEL_CASE_PATTERN.split(value)) {
+ field = (caseInsensitive ? field.toLowerCase(Locale.ENGLISH) : field);
+ builder.append((builder.length() != 0) ? StringUtils.capitalize(field) : field);
+ }
+ char lastChar = value.charAt(value.length() - 1);
+ for (char suffix : SUFFIXES) {
+ if (lastChar == suffix) {
+ builder.append(suffix);
+ break;
+ }
+ }
+ return builder.toString();
+ }
+
+ }
+
+ /**
+ * Return a {@link RelaxedNames} for the given source camelCase source name.
+ * @param name the source name in camelCase
+ * @return the relaxed names
+ */
+ public static RelaxedNames forCamelCase(String name) {
+ StringBuilder result = new StringBuilder();
+ for (char c : name.toCharArray()) {
+ result.append((Character.isUpperCase(c) && result.length() > 0 && result.charAt(result.length() - 1) != '-')
+ ? "-" + Character.toLowerCase(c) : c);
+ }
+ return new RelaxedNames(result.toString());
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/CheckActiveProfilesListener.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/CheckActiveProfilesListener.java
new file mode 100644
index 0000000..a2e36d0
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/CheckActiveProfilesListener.java
@@ -0,0 +1,48 @@
+package cn.axzo.framework.boot.env;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static cn.axzo.framework.core.Constants.*;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/8 19:24
+ **/
+@Slf4j
+public class CheckActiveProfilesListener implements ApplicationListener {
+
+ @Override
+ public void onApplicationEvent(ApplicationPreparedEvent event) {
+ boolean checkActiveProfiles = event.getApplicationContext().getEnvironment()
+ .getProperty("spring.profiles.check-active", Boolean.class, true);
+ if (!checkActiveProfiles) {
+ return;
+ }
+ ConfigurableEnvironment env = event.getApplicationContext().getEnvironment();
+ List activeProfiles = Arrays.asList(env.getActiveProfiles());
+ if (activeProfiles.contains(ENV_LOCAL) && activeProfiles.contains(ENV_PROD)) {
+ log.error(errorMsg(ENV_LOCAL, ENV_PROD));
+ }
+ if (activeProfiles.contains(ENV_DEV) && activeProfiles.contains(ENV_PROD)) {
+ log.error(errorMsg(ENV_DEV, ENV_PROD));
+ }
+ if (activeProfiles.contains(ENV_TEST) && activeProfiles.contains(ENV_PROD)) {
+ log.error(errorMsg(ENV_TEST, ENV_PROD));
+ }
+ if (activeProfiles.contains(ENV_STG) && activeProfiles.contains(ENV_PROD)) {
+ log.error(errorMsg(ENV_STG, ENV_PROD));
+ }
+ }
+
+ private String errorMsg(String env1, String env2) {
+ return "You have misconfigured your application! It should not run " +
+ "with both the '" + env1 + "' and '" + env2 + "' profiles at the same time.";
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/UnicodePropertiesPropertySourceLoader.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/UnicodePropertiesPropertySourceLoader.java
new file mode 100644
index 0000000..28f21eb
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/UnicodePropertiesPropertySourceLoader.java
@@ -0,0 +1,45 @@
+package cn.axzo.framework.boot.env;
+
+import com.google.common.collect.Lists;
+import org.springframework.boot.env.PropertySourceLoader;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.Resource;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Properties;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * @Description 使properties配置文件支持UTF_8编码
+ * @Author liyong.tian
+ * @Date 2020/9/8 19:33
+ **/
+public class UnicodePropertiesPropertySourceLoader implements PropertySourceLoader, Ordered {
+
+ @Override
+ public String[] getFileExtensions() {
+ return new String[]{"properties"};
+ }
+
+ @Override
+ public List> load(String name, Resource resource) throws IOException {
+ Properties properties = new Properties();
+ properties.load(new InputStreamReader(resource.getInputStream(), UTF_8));
+ if (!properties.isEmpty()) {
+ List> list = Lists.newArrayList();
+ list.add(new PropertiesPropertySource(name, properties));
+ return list;
+ }
+ return null;
+ }
+
+ @Override
+ public int getOrder() {
+ return HIGHEST_PRECEDENCE + LOWEST_PRECEDENCE;
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/configoverride/ConfigOverrideEnvironmentPostProcessor.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/configoverride/ConfigOverrideEnvironmentPostProcessor.java
new file mode 100644
index 0000000..5cf696d
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/configoverride/ConfigOverrideEnvironmentPostProcessor.java
@@ -0,0 +1,85 @@
+package cn.axzo.framework.boot.env.configoverride;
+
+import cn.axzo.framework.core.io.ResourceException;
+import cn.axzo.framework.core.io.Resources;
+import cn.axzo.framework.core.io.resource.Resource;
+import cn.axzo.framework.core.util.MapUtil;
+import jodd.props.Props;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import static cn.axzo.framework.core.io.ResourceStrings.CLASSPATH_ALL_URL_PREFIX;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/8 19:43
+ **/
+public class ConfigOverrideEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
+
+ /**
+ * The default order for the processor.
+ */
+ public static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 10;
+
+ private int order = DEFAULT_ORDER;
+
+ private static final String CONFIG_OVERRIDE_PROPERTY_SOURCE = "configOverride";
+
+ @Override
+ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
+ ConfigOverrideProperties overrideProperties = Binder.get(environment)
+ .bind(ConfigOverrideProperties.PREFIX, ConfigOverrideProperties.class)
+ .orElse(null);
+
+ if (overrideProperties != null) {
+ if (!overrideProperties.isEnabled()) {
+ return;
+ }
+ Props props = new Props();
+ props.setSkipEmptyProps(false);
+ String locationPattern = CLASSPATH_ALL_URL_PREFIX + "**/" + overrideProperties.getPropsFilepath();
+ Resource[] resources = Resources.findResources(locationPattern);
+ if (resources.length > 0) {
+ try (InputStream is = resources[0].getInputStream()) {
+ props.load(is);
+ } catch (IOException | ResourceException e) {
+ throw new IllegalArgumentException("cannot load config override file", e);
+ }
+ } else {
+ return;
+ }
+
+ if (props.getActiveProfiles() == null || props.getActiveProfiles().length == 0) {
+ props.setActiveProfiles(environment.getActiveProfiles());
+ } else {
+ environment.setActiveProfiles(props.getActiveProfiles());
+ }
+
+ Map map = props.innerMap(null);
+ if (MapUtil.isNotEmpty(map)) {
+ MapPropertySource propertySource = new MapPropertySource(CONFIG_OVERRIDE_PROPERTY_SOURCE, map);
+ environment.getPropertySources().addFirst(propertySource);
+ }
+ } else {
+ return;
+ }
+ }
+
+ @Override
+ public int getOrder() {
+ return order;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/configoverride/ConfigOverrideProperties.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/configoverride/ConfigOverrideProperties.java
new file mode 100644
index 0000000..86d9f9d
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/configoverride/ConfigOverrideProperties.java
@@ -0,0 +1,31 @@
+package cn.axzo.framework.boot.env.configoverride;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/8 19:37
+ **/
+@Data
+@ConfigurationProperties(prefix = ConfigOverrideProperties.PREFIX)
+public class ConfigOverrideProperties {
+
+ public final static String PREFIX = "spring.config.override";
+
+ public final static String PROPS = ".props";
+
+ // 是否允许配置重写
+ private boolean enabled;
+
+ // 在classpath下的配置文件,必须是.props格式
+ private String propsFile = "override";
+
+ public String getPropsFilepath() {
+ if (getPropsFile().endsWith(PROPS)) {
+ return getPropsFile();
+ }
+ return propsFile + PROPS;
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/devtools/DevToolsPropertyPostProcessor.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/devtools/DevToolsPropertyPostProcessor.java
new file mode 100644
index 0000000..e865593
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/devtools/DevToolsPropertyPostProcessor.java
@@ -0,0 +1,103 @@
+package cn.axzo.framework.boot.env.devtools;
+
+import cn.axzo.framework.boot.EnvironmentUtil;
+import cn.axzo.framework.boot.env.configoverride.ConfigOverrideEnvironmentPostProcessor;
+import com.google.common.collect.ImmutableMap;
+import lombok.val;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.ansi.AnsiOutput;
+import org.springframework.boot.devtools.restart.Restarter;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.MapPropertySource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.springframework.util.ClassUtils.isPresent;
+
+/**
+ * @author liyong.tian
+ * @since 2017/11/6 下午3:13
+ */
+@Order
+public class DevToolsPropertyPostProcessor implements EnvironmentPostProcessor, Ordered {
+
+ /**
+ * The default order for the processor.
+ */
+ public static final int DEFAULT_ORDER = ConfigOverrideEnvironmentPostProcessor.DEFAULT_ORDER - 1;
+
+ private int order = DEFAULT_ORDER;
+
+ private static final String REFRESH_MORE_PROPERTY_SOURCE = "refreshMore";
+
+ private static final String REFRESH_MORE_PROPERTY_WITHOUT_BOOTSTRAP_SOURCE = "refreshMoreWithoutBootstrap";
+
+ private static final Map properties;
+
+ private static final Map propertiesWithoutBootstrap;
+
+ static {
+ Map devToolsProperties = new HashMap<>();
+ devToolsProperties.put("spring.config.override.enabled", "true");
+ properties = ImmutableMap.copyOf(devToolsProperties);
+
+ devToolsProperties.clear();
+ devToolsProperties.put("spring.mvc.http-log.enabled", "true");
+ devToolsProperties.put("spring.output.ansi.enabled", AnsiOutput.Enabled.ALWAYS);
+ devToolsProperties.put("spring.messages.cache-seconds", 1);
+ propertiesWithoutBootstrap = ImmutableMap.copyOf(devToolsProperties);
+ }
+
+ @Override
+ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
+ ClassLoader classLoader = application.getClassLoader();
+ if (!isPresent("org.springframework.boot.devtools.env.DevToolsPropertyDefaultsPostProcessor", classLoader)) {
+ return;
+ }
+ if (this.isLocalApplication(environment) && this.canAddProperties(environment)) {
+ environment.getPropertySources().addLast(new MapPropertySource(REFRESH_MORE_PROPERTY_SOURCE, properties));
+
+ if (!EnvironmentUtil.isSpringCloudContext(environment)) {
+ val source = new MapPropertySource(REFRESH_MORE_PROPERTY_WITHOUT_BOOTSTRAP_SOURCE, propertiesWithoutBootstrap);
+ environment.getPropertySources().addLast(source);
+ }
+ }
+ }
+
+ private boolean isLocalApplication(ConfigurableEnvironment environment) {
+ return environment.getPropertySources().get("remoteUrl") == null;
+ }
+
+ private boolean canAddProperties(Environment environment) {
+ return this.isRestarterInitialized() || this.isRemoteRestartEnabled(environment);
+ }
+
+ private boolean isRestarterInitialized() {
+ try {
+ Restarter restarter = Restarter.getInstance();
+ return restarter != null && restarter.getInitialUrls() != null;
+ } catch (Exception ex) {
+ return false;
+ }
+ }
+
+ private boolean isRemoteRestartEnabled(Environment environment) {
+ /*RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment, "spring.devtools.remote.");
+ return resolver.containsProperty("secret");*/
+ return environment.containsProperty("spring.devtools.remote.secret");
+ }
+
+ @Override
+ public int getOrder() {
+ return order;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/LoggingConfigFixer.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/LoggingConfigFixer.java
new file mode 100644
index 0000000..ecfbc2f
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/LoggingConfigFixer.java
@@ -0,0 +1,31 @@
+package cn.axzo.framework.boot.logging;
+
+import org.springframework.util.StringUtils;
+
+import java.util.Optional;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/8 20:06
+ **/
+public class LoggingConfigFixer {
+
+ private static final String LOGGING_FILE_EXTENSION = ".log";
+
+ public Optional fixLoggingFile(String loggingPath, String loggingFile) {
+ if (loggingFile == null || loggingPath == null) {
+ return Optional.empty();
+ }
+ if (!loggingFile.endsWith(LOGGING_FILE_EXTENSION)) {
+ loggingFile = loggingFile + LOGGING_FILE_EXTENSION;
+ }
+ if (!loggingFile.startsWith(loggingPath)) {
+ if (!loggingPath.endsWith("/")) {
+ loggingPath = loggingPath + "/";
+ }
+ loggingFile = StringUtils.applyRelativePath(loggingPath, loggingFile);
+ }
+ return Optional.of(loggingFile);
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/Log4j2MDCHandler.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/Log4j2MDCHandler.java
new file mode 100644
index 0000000..f233160
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/Log4j2MDCHandler.java
@@ -0,0 +1,131 @@
+package cn.axzo.framework.boot.logging.log4j2;
+
+import cn.axzo.framework.boot.EnvironmentUtil;
+import lombok.RequiredArgsConstructor;
+import org.apache.logging.log4j.ThreadContext;
+import org.springframework.boot.logging.LogFile;
+import org.springframework.core.env.*;
+import org.springframework.util.ClassUtils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import static cn.axzo.framework.boot.PropertySourceUtils.getSubProperties;
+import static cn.axzo.framework.core.net.Inets.IP_SYSTEM_KEY;
+import static com.google.common.collect.Maps.newHashMap;
+
+/**
+ * @author liyong.tian
+ * @since 2017/9/18 下午8:45
+ */
+@RequiredArgsConstructor
+public class Log4j2MDCHandler {
+
+ private final ConfigurableEnvironment environment;
+
+ private final ClassLoader classLoader;
+
+ private static final String LOG4J2_THREAD_CONTEXT = "org.apache.logging.log4j.ThreadContext";
+
+ private static final String LOGGING_PATH_KEY = LogFile.FILE_PATH_PROPERTY;
+ private static final String DEFAULT_LOGGING_PATH = "logs";
+
+ private static final String FILE_NAME_KEY = "spring.application.name";
+ private static final String DEFAULT_FILE_NAME = "spring";
+
+ private static final String ROOT_LOGGER_LEVEL_KEY = "logging.level.root";
+ private static final String DEFAULT_ROOT_LOGGER_LEVEL = "info";
+
+ private static final String LOGGING_FILE_ENABLED_KEY = "logging.file-enabled";
+ private static final String DEFAULT_LOGGING_FILE_ENABLED = "true";
+
+ public Log4j2MDCHandler(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ this.environment = new StandardEnvironment();
+ }
+
+ public void overwrite() {
+ if (ClassUtils.isPresent(LOG4J2_THREAD_CONTEXT, classLoader)) {
+ Map properties = resolveEnvironment(environment);
+ Map systemProperties = newHashMap(environment.getSystemProperties());
+ Map systemEnvironment = newHashMap(environment.getSystemEnvironment());
+
+ properties.forEach((k, v) -> {
+ if (v != null && systemProperties.remove(k) == null && systemEnvironment.remove(k) == null) {
+ ThreadContext.put(k, v.toString());
+ }
+ });
+
+ if (!ThreadContext.containsKey(LOGGING_PATH_KEY)) {
+ ThreadContext.put(LOGGING_PATH_KEY, DEFAULT_LOGGING_PATH);
+ }
+ if (!ThreadContext.containsKey(FILE_NAME_KEY)) {
+ ThreadContext.put(FILE_NAME_KEY, DEFAULT_FILE_NAME);
+ }
+ if (!ThreadContext.containsKey(ROOT_LOGGER_LEVEL_KEY)) {
+ ThreadContext.put(ROOT_LOGGER_LEVEL_KEY, DEFAULT_ROOT_LOGGER_LEVEL);
+ }
+ if (!ThreadContext.containsKey(LOGGING_FILE_ENABLED_KEY)) {
+ ThreadContext.put(LOGGING_FILE_ENABLED_KEY, DEFAULT_LOGGING_FILE_ENABLED);
+ }
+
+ // HOST_IP
+ EnvironmentUtil.fetchLocalIp(environment).ifPresent(ip -> ThreadContext.put(IP_SYSTEM_KEY, ip));
+ }
+ }
+
+ public void setup() {
+ if (ClassUtils.isPresent(LOG4J2_THREAD_CONTEXT, classLoader)) {
+ System.setProperty("isThreadContextMapInheritable", "true");
+ }
+ }
+
+ private Map resolveEnvironment(ConfigurableEnvironment environment) {
+ //spring-boot-2.0.X版本自定义getSubProperties方法 spring-boot-1.5.X版本引用PropertySourceUtils.getSubProperties()方法
+ Map properties = getSubProperties(environment.getPropertySources(), null);
+ Map resolvedProperties = new HashMap<>();
+ properties.forEach((k, v) -> {
+ if (v instanceof String) {
+ resolvedProperties.put(k, environment.resolvePlaceholders((String) v));
+ } else {
+ resolvedProperties.put(k, v);
+ }
+ });
+ //spring-boot-1.5.X 版本
+ /*Map properties = getSubProperties(environment.getPropertySources(), null);
+ Map resolvedProperties = new HashMap<>();
+ properties.forEach((k, v) -> {
+ if (v instanceof String) {
+ resolvedProperties.put(k, environment.resolvePlaceholders((String) v));
+ } else {
+ resolvedProperties.put(k, v);
+ }
+ });*/
+ return resolvedProperties;
+ }
+
+ /*private Map getSubProperties(PropertySources propertySources) {
+ Iterator> sourceIterator = propertySources.iterator();
+ Map subProperties = new HashMap<>();
+ while (true) {
+ PropertySource source;
+ do {
+ if (sourceIterator.hasNext()) {
+ return Collections.unmodifiableMap(subProperties);
+ }
+ source = sourceIterator.next();
+ } while (!(source instanceof EnumerablePropertySource));
+
+ String[] propertyNames = ((EnumerablePropertySource) source).getPropertyNames();
+ int length = propertyNames.length;
+ for (int i = 0; i < length; ++i) {
+ String key = propertyNames[i];
+ if (key != null && !subProperties.containsKey(key)) {
+ subProperties.put(key, source.getProperty(key));
+ }
+ }
+ }
+ }*/
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/Log4j2MDCListener.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/Log4j2MDCListener.java
new file mode 100644
index 0000000..4b5355b
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/Log4j2MDCListener.java
@@ -0,0 +1,27 @@
+package cn.axzo.framework.boot.logging.log4j2;
+
+import lombok.val;
+import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
+import org.springframework.boot.context.logging.LoggingApplicationListener;
+import org.springframework.context.ApplicationListener;
+import org.springframework.core.Ordered;
+
+/**
+ * Lookup properties of Spring
+ *
+ * @author liyong.tian
+ * @since 2016/11/28
+ */
+public class Log4j2MDCListener implements ApplicationListener, Ordered {
+
+ @Override
+ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
+ val handler = new Log4j2MDCHandler(event.getEnvironment(), event.getSpringApplication().getClassLoader());
+ handler.overwrite();
+ }
+
+ @Override
+ public int getOrder() {
+ return LoggingApplicationListener.DEFAULT_ORDER - 1;
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/Log4j2MDCSetupListener.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/Log4j2MDCSetupListener.java
new file mode 100644
index 0000000..dd1075b
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/Log4j2MDCSetupListener.java
@@ -0,0 +1,26 @@
+package cn.axzo.framework.boot.logging.log4j2;
+
+import lombok.val;
+import org.springframework.boot.context.event.ApplicationStartingEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.core.Ordered;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author liyong.tian
+ * @since 2017/11/20 下午7:21
+ */
+public class Log4j2MDCSetupListener implements ApplicationListener, Ordered {
+
+ @Override
+ public void onApplicationEvent(@Nonnull ApplicationStartingEvent event) {
+ val handler = new Log4j2MDCHandler(event.getSpringApplication().getClassLoader());
+ handler.setup();
+ }
+
+ @Override
+ public int getOrder() {
+ return HIGHEST_PRECEDENCE;
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/log4j2-sample.xml b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/log4j2-sample.xml
new file mode 100644
index 0000000..2444f97
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/logging/log4j2/log4j2-sample.xml
@@ -0,0 +1,49 @@
+
+
+
+ ${ctx:logging.path}
+ ${ctx:spring.application.name}
+ ${ctx:logging.level.root}
+ ????
+ %xwEx
+ %5p
+ %clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%10t]}{faint} %clr{%c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}
+ %d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN} ${sys:PID} --- [%t] %c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/script/AbstractScript.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/script/AbstractScript.java
new file mode 100644
index 0000000..317fc95
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/script/AbstractScript.java
@@ -0,0 +1,37 @@
+package cn.axzo.framework.boot.script;
+
+import lombok.val;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/8 19:56
+ **/
+public abstract class AbstractScript {
+
+ private AtomicBoolean executed = new AtomicBoolean(false);
+
+ void process() {
+ //确保只执行一次
+ if (executed.compareAndSet(false, true)) {
+ val timer = new Timer();
+ timer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ execute();
+ timer.cancel();
+ }
+ }, getDelayMills());
+ }
+ }
+
+ abstract protected void execute();
+
+ protected long getDelayMills() {
+ return 15000;
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/script/ScriptListener.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/script/ScriptListener.java
new file mode 100644
index 0000000..8f98dbe
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/script/ScriptListener.java
@@ -0,0 +1,27 @@
+package cn.axzo.framework.boot.script;
+
+import lombok.extern.slf4j.Slf4j;
+import lombok.val;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.ApplicationListener;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/8 19:58
+ **/
+@Slf4j
+public class ScriptListener implements ApplicationListener {
+
+ @Override
+ public void onApplicationEvent(ApplicationReadyEvent event) {
+ val scripts = event.getApplicationContext().getBeansOfType(AbstractScript.class).values();
+ scripts.parallelStream().forEach(script -> {
+ try {
+ script.process();
+ } catch (Exception e) {
+ log.error("script execute error", e);
+ }
+ });
+ }
+}
diff --git a/axzo-common-boot/src/main/java/cn/axzo/framework/boot/system/SystemPropertiesListener.java b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/system/SystemPropertiesListener.java
new file mode 100644
index 0000000..7f59b96
--- /dev/null
+++ b/axzo-common-boot/src/main/java/cn/axzo/framework/boot/system/SystemPropertiesListener.java
@@ -0,0 +1,68 @@
+package cn.axzo.framework.boot.system;
+
+import cn.axzo.framework.boot.EnvironmentUtil;
+import cn.axzo.framework.core.FetchException;
+import cn.axzo.framework.core.net.Inets;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
+import org.springframework.boot.context.logging.LoggingApplicationListener;
+import org.springframework.context.ApplicationListener;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.util.StopWatch;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/8 20:00
+ **/
+@Slf4j
+public class SystemPropertiesListener implements ApplicationListener, Ordered {
+
+ private AtomicBoolean executed = new AtomicBoolean(false);
+
+ @Override
+ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
+ ConfigurableEnvironment env = event.getEnvironment();
+
+ // don't listen to events in a spring cloud context
+ if (EnvironmentUtil.isSpringCloudContext(env)) {
+ return;
+ }
+
+ // Think about twice invoking this listener
+ if (executed.get()) {
+ return;
+ }
+
+ StopWatch watch = new StopWatch("setup system properties");
+ if (env.containsProperty("system.properties.fetch-local-ip")) {
+ watch.start("fetch local ip");
+ fetchLocalIp(EnvironmentUtil.fetchLocalIpTimeoutSeconds(env));
+ watch.stop();
+ }
+
+ if (watch.getTaskCount() > 0) {
+ log.info(watch.prettyPrint());
+ }
+
+ executed.set(true);
+ }
+
+ private void fetchLocalIp(int timeoutSeconds) {
+ log.info("Fetching local ip....");
+ try {
+ System.setProperty(Inets.IP_SYSTEM_KEY, Inets.fetchLocalIp(timeoutSeconds));
+ log.info("Local ip: " + Inets.fetchLocalIp());
+ } catch (FetchException e) {
+ log.info("Local ip: cannot fetch in " + timeoutSeconds + " seconds");
+ }
+ }
+
+ @Override
+ public int getOrder() {
+ return LoggingApplicationListener.DEFAULT_ORDER + 1;
+ }
+}
diff --git a/axzo-common-clients/pom.xml b/axzo-common-clients/pom.xml
new file mode 100644
index 0000000..be6b81a
--- /dev/null
+++ b/axzo-common-clients/pom.xml
@@ -0,0 +1,21 @@
+
+
+ 4.0.0
+
+
+ axzo-framework-commons
+ cn.axzo.framework
+ 1.0.0-SNAPSHOT
+
+
+ cn.axzo.framework.client
+ axzo-common-clients
+ pom
+ Axzo Common Client Parent
+
+
+ retrofit-starter
+
+
\ No newline at end of file
diff --git a/axzo-common-clients/retrofit-starter/pom.xml b/axzo-common-clients/retrofit-starter/pom.xml
new file mode 100644
index 0000000..c84f001
--- /dev/null
+++ b/axzo-common-clients/retrofit-starter/pom.xml
@@ -0,0 +1,38 @@
+
+
+ 4.0.0
+
+
+ cn.axzo.framework.client
+ axzo-common-clients
+ 1.0.0-SNAPSHOT
+
+
+ retrofit-starter
+ Axzo Common Client Retrofit Starter
+
+
+
+ cn.axzo.framework
+ axzo-common-domain
+
+
+ cn.axzo.framework.jackson
+ jackson-starter
+
+
+ com.squareup.retrofit2
+ retrofit
+
+
+ com.squareup.retrofit2
+ converter-jackson
+
+
+ com.squareup.retrofit2
+ adapter-rxjava2
+
+
+
\ No newline at end of file
diff --git a/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/FastRetrofit.java b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/FastRetrofit.java
new file mode 100644
index 0000000..6b0d3c3
--- /dev/null
+++ b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/FastRetrofit.java
@@ -0,0 +1,33 @@
+package cn.axzo.framework.client.retrofit;
+
+import cn.axzo.framework.jackson.utility.JSON;
+import lombok.experimental.UtilityClass;
+import okhttp3.OkHttpClient;
+import retrofit2.Retrofit;
+import retrofit2.converter.jackson.JacksonConverterFactory;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/11 15:41
+ **/
+@UtilityClass
+public class FastRetrofit {
+
+ public T target(String baseUrl, Class targetType) {
+ OkHttpClient client = new OkHttpClient.Builder()
+ .connectTimeout(5, SECONDS)
+ .readTimeout(5, SECONDS)
+ .writeTimeout(5, SECONDS)
+ .addInterceptor(new HttpLogInterceptor())
+ .build();
+ return new Retrofit.Builder()
+ .client(client)
+ .baseUrl(baseUrl)
+ .addConverterFactory(JacksonConverterFactory.create(JSON.objectMapper()))
+ .build()
+ .create(targetType);
+ }
+}
diff --git a/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/HttpLogInterceptor.java b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/HttpLogInterceptor.java
new file mode 100644
index 0000000..4de9a36
--- /dev/null
+++ b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/HttpLogInterceptor.java
@@ -0,0 +1,132 @@
+package cn.axzo.framework.client.retrofit;
+
+import cn.axzo.framework.domain.http.*;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static okhttp3.Protocol.HTTP_1_1;
+
+/**
+ * @Description 日志拦截器
+ * @Author liyong.tian
+ * @Date 2020/9/11 15:42
+ **/
+@Slf4j
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class HttpLogInterceptor implements Interceptor {
+
+ private final HttpLogFormatter formatter;
+
+ private final String[] ignoreUrlPatterns;
+
+ public HttpLogInterceptor() {
+ this(JsonHttpLogFormatter.INSTANCE);
+ }
+
+ public HttpLogInterceptor(String... ignoreUrlPatterns) {
+ this(JsonHttpLogFormatter.INSTANCE, ignoreUrlPatterns);
+ }
+
+ public HttpLogInterceptor(HttpLogFormatter formatter, String... ignoreUrlPatterns) {
+ if (formatter == null) {
+ this.formatter = JsonHttpLogFormatter.INSTANCE;
+ } else {
+ this.formatter = formatter;
+ }
+ this.ignoreUrlPatterns = ignoreUrlPatterns;
+ }
+
+ @Override
+ @ParametersAreNonnullByDefault
+ public Response intercept(Chain chain) throws IOException {
+ return null;
+ }
+
+ /**
+ * 输出正常响应日志
+ */
+ private HttpResponseLog _responseLog(@NonNull Response response, long tookMs) {
+ HttpResponseLog.HttpResponseLogBuilder logBuilder = HttpResponseLog.builder();
+ // code
+ logBuilder.status(response.code());
+
+ // msg
+ logBuilder.msg(response.message());
+
+ // url
+ logBuilder.url(Objects.toString(response.request().url()));
+
+ // tookMs
+ logBuilder.tookMs(tookMs);
+
+ // header
+ Headers headers = response.headers();
+ List responseHeaders = new ArrayList<>();
+ for (int i = 0, count = headers.size(); i < count; i++) {
+ responseHeaders.add(headers.name(i) + ": " + headers.value(i));
+ }
+ logBuilder.headers(responseHeaders);
+
+ // body
+ if (!HttpHeaderUtil.isDownloadResponse(responseHeaders)) {
+ logBuilder.body(OkHttpUtil.getBody(response).orElse(null));
+ }
+
+ return logBuilder.build();
+ }
+
+ /**
+ * 输出异常响应日志
+ */
+ private HttpResponseLog _responseLog(@NonNull Request request, long tookMs, Exception e) {
+ HttpResponseLog.HttpResponseLogBuilder logBuilder = HttpResponseLog.builder();
+ // url
+ logBuilder.url(Objects.toString(request.url()));
+
+ // errorMsg
+ logBuilder.errorMsg(e.getMessage());
+
+ // tookMs
+ logBuilder.tookMs(tookMs);
+
+ return logBuilder.build();
+ }
+
+ /**
+ * 输出请求日志
+ */
+ private HttpRequestLog _requestLog(Connection connection, @NonNull Request request) {
+ HttpRequestLog.HttpRequestLogBuilder logBuilder = HttpRequestLog.builder();
+ // protocol
+ Protocol protocol = connection != null ? connection.protocol() : HTTP_1_1;
+ logBuilder.protocol(Objects.toString(protocol));
+
+ // method
+ logBuilder.method(request.method());
+
+ // url
+ logBuilder.url(Objects.toString(request.url()));
+
+ // headers
+ Headers headers = request.headers();
+ List requestHeaders = new ArrayList<>();
+ for (int i = 0, count = headers.size(); i < count; i++) {
+ requestHeaders.add(headers.name(i) + ": " + headers.value(i));
+ }
+ logBuilder.headers(requestHeaders);
+
+ // body
+ if (!HttpHeaderUtil.isMultipartRequest(requestHeaders)) {
+ logBuilder.body(OkHttpUtil.getBody(request).orElse(null));
+ }
+
+ return logBuilder.build();
+ }
+}
diff --git a/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/MediaTypes.java b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/MediaTypes.java
new file mode 100644
index 0000000..085bfa5
--- /dev/null
+++ b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/MediaTypes.java
@@ -0,0 +1,13 @@
+package cn.axzo.framework.client.retrofit;
+
+import okhttp3.MediaType;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/11 15:41
+ **/
+public interface MediaTypes {
+
+ MediaType APPLICATION_JSON = MediaType.parse("application/json; charset=UTF-8");
+}
diff --git a/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/OkHttpUtil.java b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/OkHttpUtil.java
new file mode 100644
index 0000000..c736e49
--- /dev/null
+++ b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/OkHttpUtil.java
@@ -0,0 +1,126 @@
+package cn.axzo.framework.client.retrofit;
+
+import okhttp3.*;
+import okhttp3.internal.http.HttpHeaders;
+import okio.Buffer;
+import okio.BufferedSource;
+
+import java.io.EOFException;
+import java.nio.charset.Charset;
+import java.util.Optional;
+
+import static java.lang.Character.isISOControl;
+import static java.lang.Long.MAX_VALUE;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Optional.empty;
+import static java.util.Optional.of;
+
+/**
+ * @Description OkHttp工具类
+ * @Author liyong.tian
+ * @Date 2020/9/11 15:46
+ **/
+public class OkHttpUtil {
+
+ /**
+ * 获取请求体内容
+ *
+ * @param request 请求对象
+ * @apiNote 如果出现异常, 则忽略, 并返回empty
+ */
+ public static Optional getBody(Request request) {
+ if (request == null || _bodyEncoded(request.headers())) {
+ return empty();
+ }
+ RequestBody body = request.body();
+ if (body == null) {
+ return empty();
+ }
+ try {
+ // 1.写入buffer
+ Buffer buffer = new Buffer();
+ body.writeTo(buffer);
+ // 2.指定字符集
+ MediaType contentType = body.contentType();
+ Charset charset = contentType == null ? UTF_8 : contentType.charset(UTF_8);
+ // 3.从buffer中读取字符串
+ if (!_isPlaintext(buffer)) {
+ return empty();
+ }
+ assert charset != null;
+ return of(buffer.clone().readString(charset));
+ } catch (Exception e) {
+ // 忽略并返回empty
+ return empty();
+ }
+ }
+
+ /**
+ * 获取响应体内容
+ *
+ * @param response 响应对象
+ */
+ public static Optional getBody(Response response) {
+ if (response == null || !HttpHeaders.hasBody(response) || _bodyEncoded(response.headers())) {
+ return empty();
+ }
+ return getBody(response.body());
+ }
+
+ /**
+ * 获取响应体内容
+ *
+ * @param responseBody 响应体对象
+ */
+ public static Optional getBody(ResponseBody responseBody) {
+ if (responseBody == null) {
+ return empty();
+ }
+ try {
+ // 1.获取buffer
+ BufferedSource source = responseBody.source();
+ source.request(MAX_VALUE); // Buffer the entire body.
+ Buffer buffer = source.buffer();
+ // 2.指定字符集
+ MediaType contentType = responseBody.contentType();
+ Charset charset = contentType == null ? UTF_8 : contentType.charset(UTF_8);
+ // 3.从buffer中读取字符串
+ if (responseBody.contentLength() == 0) {
+ return empty();
+ }
+ assert charset != null;
+ return of(buffer.clone().readString(charset));
+ } catch (Exception e) {
+ return empty();
+ }
+ }
+
+ /**
+ * Returns true if the body in question probably contains human readable text. Uses a small sample
+ * of code points to detect unicode control characters commonly used in binary file signatures.
+ */
+ private static boolean _isPlaintext(Buffer buffer) {
+ try {
+ Buffer prefix = new Buffer();
+ long byteCount = buffer.size() < 64 ? buffer.size() : 64;
+ buffer.copyTo(prefix, 0, byteCount);
+ for (int i = 0; i < 16; i++) {
+ if (prefix.exhausted()) {
+ break;
+ }
+ if (isISOControl(prefix.readUtf8CodePoint())) {
+ return false;
+ }
+ }
+ return true;
+ } catch (EOFException e) {
+ // 忽略这个异常
+ return false; // Truncated UTF-8 sequence.
+ }
+ }
+
+ private static boolean _bodyEncoded(Headers headers) {
+ String contentEncoding = headers.get("Content-Encoding");
+ return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
+ }
+}
diff --git a/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/Retrofits.java b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/Retrofits.java
new file mode 100644
index 0000000..ce555ac
--- /dev/null
+++ b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/Retrofits.java
@@ -0,0 +1,52 @@
+package cn.axzo.framework.client.retrofit;
+
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+import retrofit2.Call;
+import retrofit2.Response;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.function.Function;
+
+import static cn.axzo.framework.core.Constants.CLIENT_MARKER;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/11 15:37
+ **/
+@Slf4j
+@UtilityClass
+public class Retrofits {
+
+ @Nonnull
+ public T execute(Call call, Function fallbackHandler) {
+ try {
+ return execute(call);
+ } catch (IOException e) {
+ return fallbackHandler.apply(e);
+ }
+ }
+
+ @Nonnull
+ public T execute(Call call) throws IOException {
+ try {
+ Response response = call.execute();
+ if (response.isSuccessful()) {
+ T t = response.body();
+ if (t == null) {
+ log.error(CLIENT_MARKER, "response body is null");
+ throw new IOException("response body is null");
+ }
+ return t;
+ } else {
+ log.error(CLIENT_MARKER, "response error, status = {}, message = {}", response.code(), response.message());
+ throw new IOException("response error");
+ }
+ } catch (IOException e) {
+ log.error(CLIENT_MARKER, "network error", e);
+ throw new IOException("network error", e);
+ }
+ }
+}
diff --git a/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/JacksonRequestBodyConverter.java b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/JacksonRequestBodyConverter.java
new file mode 100644
index 0000000..87bb118
--- /dev/null
+++ b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/JacksonRequestBodyConverter.java
@@ -0,0 +1,30 @@
+package cn.axzo.framework.client.retrofit.converter;
+
+import cn.axzo.framework.client.retrofit.MediaTypes;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import okhttp3.RequestBody;
+import retrofit2.Converter;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.io.IOException;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/11 15:49
+ **/
+@ParametersAreNonnullByDefault
+public class JacksonRequestBodyConverter implements Converter {
+
+ private final ObjectWriter adapter;
+
+ public JacksonRequestBodyConverter(ObjectWriter adapter) {
+ this.adapter = adapter;
+ }
+
+ @Override
+ public RequestBody convert(T value) throws IOException {
+ byte[] bytes = adapter.writeValueAsBytes(value);
+ return RequestBody.create(MediaTypes.APPLICATION_JSON, bytes);
+ }
+}
diff --git a/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/JacksonResponseBodyConverter.java b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/JacksonResponseBodyConverter.java
new file mode 100644
index 0000000..a9a55fe
--- /dev/null
+++ b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/JacksonResponseBodyConverter.java
@@ -0,0 +1,32 @@
+package cn.axzo.framework.client.retrofit.converter;
+
+import com.fasterxml.jackson.databind.ObjectReader;
+import okhttp3.ResponseBody;
+import retrofit2.Converter;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.io.IOException;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/11 15:51
+ **/
+@ParametersAreNonnullByDefault
+public class JacksonResponseBodyConverter implements Converter {
+
+ private final ObjectReader adapter;
+
+ public JacksonResponseBodyConverter(ObjectReader adapter) {
+ this.adapter = adapter;
+ }
+
+ @Override
+ public T convert(ResponseBody value) throws IOException {
+ try {
+ return adapter.readValue(value.charStream());
+ } finally {
+ value.close();
+ }
+ }
+}
diff --git a/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/RawRequestBodyConverter.java b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/RawRequestBodyConverter.java
new file mode 100644
index 0000000..31175fc
--- /dev/null
+++ b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/RawRequestBodyConverter.java
@@ -0,0 +1,27 @@
+package cn.axzo.framework.client.retrofit.converter;
+
+import lombok.RequiredArgsConstructor;
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+import retrofit2.Converter;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.nio.charset.Charset;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/11 15:52
+ **/
+@RequiredArgsConstructor
+@ParametersAreNonnullByDefault
+public class RawRequestBodyConverter implements Converter {
+
+ private final MediaType contentType;
+
+ @Override
+ public RequestBody convert(String s) {
+ byte[] bytes = s.getBytes(Charset.defaultCharset());
+ return RequestBody.create(contentType, bytes);
+ }
+}
diff --git a/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/RawResponseBodyConverter.java b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/RawResponseBodyConverter.java
new file mode 100644
index 0000000..53f784f
--- /dev/null
+++ b/axzo-common-clients/retrofit-starter/src/main/java/cn/axzo/framework/client/retrofit/converter/RawResponseBodyConverter.java
@@ -0,0 +1,21 @@
+package cn.axzo.framework.client.retrofit.converter;
+
+import okhttp3.ResponseBody;
+import retrofit2.Converter;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.io.IOException;
+
+/**
+ * @Description
+ * @Author liyong.tian
+ * @Date 2020/9/11 15:53
+ **/
+@ParametersAreNonnullByDefault
+public class RawResponseBodyConverter implements Converter {
+
+ @Override
+ public String convert(ResponseBody value) throws IOException {
+ return value.string();
+ }
+}
diff --git a/axzo-common-core/pom.xml b/axzo-common-core/pom.xml
new file mode 100644
index 0000000..3956f45
--- /dev/null
+++ b/axzo-common-core/pom.xml
@@ -0,0 +1,64 @@
+
+
+ 4.0.0
+
+
+ axzo-framework-commons
+ cn.axzo.framework
+ 1.0.0-SNAPSHOT
+
+
+ axzo-common-core
+ Axzo Common Core
+
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+
+
+ org.jooq
+ jool
+
+
+
+ org.jooq
+ joor
+
+
+
+ org.jodd
+ jodd-bean
+
+
+
+ org.javatuples
+ javatuples
+
+
+
+ javax.validation
+ validation-api
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-log4j2
+ test
+
+
+
diff --git a/axzo-common-core/src/main/java/cn/axzo/framework/core/Constants.java b/axzo-common-core/src/main/java/cn/axzo/framework/core/Constants.java
new file mode 100644
index 0000000..aedb552
--- /dev/null
+++ b/axzo-common-core/src/main/java/cn/axzo/framework/core/Constants.java
@@ -0,0 +1,95 @@
+package cn.axzo.framework.core;
+
+import org.slf4j.Marker;
+import org.slf4j.MarkerFactory;
+
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 通用常量
+ *
+ * @author liyong.tian
+ * @since 2020/8/12 16:13
+ */
+public interface Constants {
+
+ /**
+ * 时间
+ */
+ String PATTERN_DATE = "yyyy-MM-dd";
+ String PATTERN_DATE_COMPACT = "yyyyMMdd";
+ String PATTERN_DATE_HOUR_COMPACT = "yyyyMMddHH";
+ String PATTERN_TIME = "HH:mm:ss";
+ String PATTERN_DATE_TIME = "yyyy-MM-dd HH:mm:ss";
+ String PATTERN_DATE_TIME_COMPACT = "yyyyMMddHHmmss";
+ String PATTERN_DATE_TIME_MILLS = "yyyy-MM-dd HH:mm:ss.SSS";
+ String PATTERN_DATE_TIME_MILLS_COMPACT = "yyMMddHHmmssSSS";
+
+ DateTimeFormatter FORMATTER_DATE_COMPACT = DateTimeFormatter.ofPattern(PATTERN_DATE_COMPACT);
+ DateTimeFormatter FORMATTER_DATE_HOUR_COMPACT = DateTimeFormatter.ofPattern(PATTERN_DATE_HOUR_COMPACT);
+ DateTimeFormatter FORMATTER_DATE_TIME_COMPACT = DateTimeFormatter.ofPattern(PATTERN_DATE_TIME_COMPACT);
+ DateTimeFormatter FORMATTER_DATE_TIME_MILLS_COMPACT = DateTimeFormatter.ofPattern(PATTERN_DATE_TIME_MILLS_COMPACT);
+
+ /**
+ * 线程池,除了IO密集型任务,2~4倍可获得最佳性能
+ */
+ Integer POOL_SIZE_SM = Runtime.getRuntime().availableProcessors();
+ Integer POOL_SIZE_MD = POOL_SIZE_SM * 2;
+ Integer POOL_SIZE_LG = POOL_SIZE_SM * 4;
+
+ /**
+ * 日志
+ */
+ String MARKER_API = "api";
+ Marker API_MARKER = MarkerFactory.getMarker(MARKER_API);
+
+ String MARKER_CLIENT = "client";
+ Marker CLIENT_MARKER = MarkerFactory.getMarker(MARKER_CLIENT);
+
+ String MARKER_JOB = "job";
+ Marker JOB_MARKER = MarkerFactory.getMarker(MARKER_JOB);
+
+ /**
+ * 环境
+ */
+ String ENV_LOCAL = "local";
+ String ENV_DEV = "dev";
+ String ENV_DEVMOBILE = "devmobile";
+ String ENV_DAILY = "daily";
+ String ENV_TEST = "test";
+ String ENV_FAT = "fat";
+ String ENV_UAT = "uat";
+ String ENV_STG = "stg";
+ String ENV_PRE = "pre";
+ String ENV_PROD = "prod";
+ String ENV_PRO = "pro";
+ String ENV_GTAPRO = "gtapro";
+ String ENV_PROMOBILE = "promobile";
+ String[] ENV_ALL = {ENV_LOCAL, ENV_DEV, ENV_DEVMOBILE, ENV_DAILY, ENV_TEST, ENV_FAT, ENV_UAT, ENV_STG, ENV_PRE, ENV_PROD, ENV_PRO, ENV_GTAPRO, ENV_PROMOBILE};
+
+ /**
+ * MVC
+ */
+ String URI_PATTERN_FILTER_ALL = "/*";
+ String URI_PATTERN_INTERCEPTOR_ALL = "/**";
+
+ /**
+ * Secure
+ */
+ String SYSTEM_ACCOUNT = "system";
+ String ANONYMOUS_USER = "anonymoususer";
+
+ /**
+ * Locale
+ */
+ String DEFAULT_LANGUAGE = "zh-cn";
+
+ /**
+ * String
+ */
+ int DEFAULT_MIN_LENGTH = 1;
+ int DEFAULT_SHORT_LENGTH = 64;
+ int DEFAULT_MEDIUM_LENGTH = 256;
+ int DEFAULT_LONG_LENGTH = 1024;
+ int DEFAULT_LARGE_LENGTH = 4096;
+}
diff --git a/axzo-common-core/src/main/java/cn/axzo/framework/core/FetchException.java b/axzo-common-core/src/main/java/cn/axzo/framework/core/FetchException.java
new file mode 100644
index 0000000..851bcdd
--- /dev/null
+++ b/axzo-common-core/src/main/java/cn/axzo/framework/core/FetchException.java
@@ -0,0 +1,21 @@
+package cn.axzo.framework.core;
+
+/**
+ * 获取资源异常
+ * @author liyong.tian
+ * @since 2020/8/12 16:16
+ */
+public class FetchException extends Exception{
+
+ public FetchException(String message) {
+ super(message);
+ }
+
+ public FetchException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FetchException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/axzo-common-core/src/main/java/cn/axzo/framework/core/IName.java b/axzo-common-core/src/main/java/cn/axzo/framework/core/IName.java
new file mode 100644
index 0000000..b0b9d4f
--- /dev/null
+++ b/axzo-common-core/src/main/java/cn/axzo/framework/core/IName.java
@@ -0,0 +1,9 @@
+package cn.axzo.framework.core;
+
+/**
+ * @author liyong.tian
+ * @since 2020/8/12 16:17
+ */
+public interface IName {
+ String getName();
+}
diff --git a/axzo-common-core/src/main/java/cn/axzo/framework/core/InternalException.java b/axzo-common-core/src/main/java/cn/axzo/framework/core/InternalException.java
new file mode 100644
index 0000000..bef4f7a
--- /dev/null
+++ b/axzo-common-core/src/main/java/cn/axzo/framework/core/InternalException.java
@@ -0,0 +1,21 @@
+package cn.axzo.framework.core;
+
+/**
+ * 内部异常
+ * @author liyong.tian
+ * @since 2020/8/12 16:18
+ */
+public class InternalException extends RuntimeException {
+
+ public InternalException(String message) {
+ super(message);
+ }
+
+ public InternalException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public InternalException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/axzo-common-core/src/main/java/cn/axzo/framework/core/NamingStrategy.java b/axzo-common-core/src/main/java/cn/axzo/framework/core/NamingStrategy.java
new file mode 100644
index 0000000..9a85068
--- /dev/null
+++ b/axzo-common-core/src/main/java/cn/axzo/framework/core/NamingStrategy.java
@@ -0,0 +1,51 @@
+package cn.axzo.framework.core;
+
+import javax.validation.constraints.NotNull;
+import java.util.Objects;
+
+/**
+ * @author liyong.tian
+ * @since 2020/8/12 16:18
+ */
+public class NamingStrategy {
+
+ public static final NamingStrategy SAME_CASE = new NamingStrategy();
+
+ public static final NamingStrategy SNAKE_CASE = new SnakeCaseStrategy();
+
+ @NotNull
+ public String translate(String name) {
+ Objects.requireNonNull(name);
+ return name;
+ }
+
+ private static class SnakeCaseStrategy extends NamingStrategy {
+ @Override
+ public String translate(String name) {
+ Objects.requireNonNull(name);
+ int length = name.length();
+ StringBuilder result = new StringBuilder(length * 2);
+ int resultLength = 0;
+ boolean wasPrevTranslated = false;
+ for (int i = 0; i < length; i++) {
+ char c = name.charAt(i);
+ if (i > 0 || c != '_') // skip first starting underscore
+ {
+ if (Character.isUpperCase(c)) {
+ if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_') {
+ result.append('_');
+ resultLength++;
+ }
+ c = Character.toLowerCase(c);
+ wasPrevTranslated = true;
+ } else {
+ wasPrevTranslated = false;
+ }
+ result.append(c);
+ resultLength++;
+ }
+ }
+ return resultLength > 0 ? result.toString() : name;
+ }
+ }
+}
diff --git a/axzo-common-core/src/main/java/cn/axzo/framework/core/RegexPool.java b/axzo-common-core/src/main/java/cn/axzo/framework/core/RegexPool.java
new file mode 100644
index 0000000..84dac34
--- /dev/null
+++ b/axzo-common-core/src/main/java/cn/axzo/framework/core/RegexPool.java
@@ -0,0 +1,14 @@
+package cn.axzo.framework.core;
+
+/**
+ * @author liyong.tian
+ * @since 2020/8/12 16:20
+ */
+public interface RegexPool {
+
+ String LOGIN_REGEX = "^[_'.@A-Za-z0-9-]*$";
+
+ String ID_REGEX = "^[_'.@A-Za-z0-9-]*$";
+
+ String ASCII_PATTERN = "^[\\x00-\\xff]+$";
+}
diff --git a/axzo-common-core/src/main/java/cn/axzo/framework/core/annotation/Description.java b/axzo-common-core/src/main/java/cn/axzo/framework/core/annotation/Description.java
new file mode 100644
index 0000000..692587c
--- /dev/null
+++ b/axzo-common-core/src/main/java/cn/axzo/framework/core/annotation/Description.java
@@ -0,0 +1,17 @@
+package cn.axzo.framework.core.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @author liyong.tian
+ * @since 2020/8/12 16:21
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Description {
+
+ String value();
+
+ String scope() default "Default";
+}
diff --git a/axzo-common-core/src/main/java/cn/axzo/framework/core/concurrent/BatchHelper.java b/axzo-common-core/src/main/java/cn/axzo/framework/core/concurrent/BatchHelper.java
new file mode 100644
index 0000000..91e3fd9
--- /dev/null
+++ b/axzo-common-core/src/main/java/cn/axzo/framework/core/concurrent/BatchHelper.java
@@ -0,0 +1,195 @@
+package cn.axzo.framework.core.concurrent;
+
+import cn.axzo.framework.core.Constants;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Collection;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import static java.lang.String.format;
+import static java.lang.System.currentTimeMillis;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.stream.Collectors.toList;
+
+/**
+ * 批处理任务工具
+ *
+ * @author liyong.tian
+ * @since 2020/8/12 16:24
+ */
+@Slf4j
+public class BatchHelper {
+
+ private static final ExecutorService DEFAULT_POOL = Executors.newFixedThreadPool(Constants.POOL_SIZE_MD);
+
+ private static final Integer DEFAULT_ERROR_THRESHOLD_PERCENTAGE = 100;
+
+ private static final long DEFAULT_TIMEOUT = 1;
+
+ private static final TimeUnit DEFAULT_UNIT = MINUTES;
+
+ @Getter
+ private final ExecutorService pool;
+
+ private final String batchName;
+
+ private final boolean autoShutdown;
+
+ private final long timeout;
+
+ private final TimeUnit unit;
+
+ // 当任务出错的数量 > 总任务数 * 该百分比,则不再处理后续任务
+ private final Integer errorThresholdPercentage;
+
+ public BatchHelper(String batchName) {
+ this(DEFAULT_POOL, batchName);
+ }
+
+ public BatchHelper(ExecutorService pool, String batchName) {
+ this(pool, batchName, DEFAULT_ERROR_THRESHOLD_PERCENTAGE);
+ }
+
+
+ public BatchHelper(ExecutorService pool, String batchName, Integer errorThresholdPercentage) {
+ this(pool, batchName, DEFAULT_TIMEOUT, DEFAULT_UNIT, errorThresholdPercentage);
+ }
+
+ /**
+ * @param nThreads 建议设置为CPU核心数的1~4倍,可获得最佳性能
+ * @param batchName 批处理名称
+ */
+ public BatchHelper(int nThreads, String batchName) {
+ this(nThreads, batchName, false);
+ }
+
+ public BatchHelper(int nThreads, String batchName, boolean autoShutdown) {
+ this(nThreads, batchName, autoShutdown, DEFAULT_ERROR_THRESHOLD_PERCENTAGE);
+ }
+
+ public BatchHelper(int nThreads, String batchName, boolean autoShutdown, Integer errorThresholdPercentage) {
+ this(nThreads, batchName, autoShutdown, DEFAULT_TIMEOUT, DEFAULT_UNIT, errorThresholdPercentage);
+ }
+
+ private BatchHelper(ExecutorService pool, String batchName, long timeout, TimeUnit unit,
+ Integer errorThresholdPercentage) {
+ this.batchName = batchName;
+ this.autoShutdown = false;
+ this.pool = pool;
+ this.timeout = timeout;
+ this.unit = unit;
+ validatePercentage(errorThresholdPercentage);
+ this.errorThresholdPercentage = errorThresholdPercentage;
+ }
+
+ public BatchHelper(int nThreads, String batchName, boolean autoShutdown, long timeout, TimeUnit unit,
+ Integer errorThresholdPercentage) {
+ if (nThreads < 1) {
+ throw new IllegalArgumentException("线程数必须大于等于1");
+ }
+ if (nThreads == 1) {
+ this.pool = Executors.newSingleThreadExecutor();
+ } else {
+ this.pool = Executors.newFixedThreadPool(nThreads);
+ }
+ this.batchName = batchName;
+ this.autoShutdown = autoShutdown;
+ this.timeout = timeout;
+ this.unit = unit;
+ validatePercentage(errorThresholdPercentage);
+ this.errorThresholdPercentage = errorThresholdPercentage;
+ }
+
+ public void process(Collection collection, Consumer handler) {
+ process(collection, handler, t -> {
+ });
+ }
+
+ public void process(Collection collection, Consumer handler, Consumer finallyHandler) {
+ process(collection, handler, (t, e) -> log.error(Constants.JOB_MARKER, format("%s error, data = %s", batchName, t)), finallyHandler);
+ }
+
+ public void process(Collection collection, Consumer handler, BiConsumer onError) {
+ process(collection, handler, onError, t -> {
+ });
+ }
+
+ /**
+ * 执行批处理(同步方法)
+ *
+ * 注: 该方法会阻塞当前线程, 直到批处理完成
+ *
+ * @param collection 待处理记录
+ * @param handler 单条处理方法
+ * @param onError 自定义单条处理异常后的行为
+ */
+ public void process(Collection collection, Consumer handler, BiConsumer onError,
+ Consumer finallyHandler) {
+ if (pool.isShutdown()) {
+ throw new RejectedExecutionException("线程池不可用");
+ }
+
+ long startTime = currentTimeMillis();
+ log.info(Constants.JOB_MARKER, format("%s querySize = %d", batchName, collection.size()));
+
+ AtomicInteger success = new AtomicInteger(0);
+ AtomicInteger error = new AtomicInteger(0);
+ Integer errorLimitInclude = collection.size() * errorThresholdPercentage / 100;
+
+ Collection> callableList = collection.stream().map(t -> (Callable