基础类库和框架实践初始化

This commit is contained in:
tianliyong 2022-10-21 17:10:34 +08:00
parent 7a7a77b0fa
commit bb090ffd40
368 changed files with 25303 additions and 0 deletions

39
.gitignore vendored Normal file
View File

@ -0,0 +1,39 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Other ###
*.log
logs/
.DS_Store
.flattened-pom.xml

View File

@ -0,0 +1,99 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>axzo-framework-commons</artifactId>
<groupId>cn.axzo.framework</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>axzo-common-autoconfigure</artifactId>
<name>Axzo Common AutoConfigure</name>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-common-boot</artifactId>
</dependency>
<!--Optional-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.axzo.framework.jackson</groupId>
<artifactId>jackson-starter</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-common-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-bean-validators</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,37 @@
package cn.axzo.framework.autoconfigure.data;
import cn.axzo.framework.domain.data.IdHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
/**
* @Description
* @Author liyong.tian
* @Date 2020/11/13 11:45
**/
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class, IdHelper.class})
public class IdAutoConfiguration {
@Value("${id.generator.node-id:}")
private Integer nodeId;
@Value("${id.generator.base-timestamp:}")
private Long baseTimestamp;
@Value("${id.generator.sequence-bits:}")
private Integer sequenceBits;
@PostConstruct
public void init() {
IdHelper.reload(nodeId, baseTimestamp, sequenceBits);
}
}

View File

@ -0,0 +1,138 @@
package cn.axzo.framework.autoconfigure.env;
import cn.axzo.framework.boot.EnvironmentUtil;
import cn.axzo.framework.core.FetchException;
import cn.axzo.framework.core.io.Resources;
import cn.axzo.framework.core.net.Inets;
import cn.axzo.framework.context.Placeholders;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.jooq.lambda.Seq;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.web.server.Ssl;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.Servlet;
import java.util.Arrays;
import java.util.Optional;
import static cn.axzo.framework.boot.DefaultProfileUtil.getActiveProfiles;
import static cn.axzo.framework.core.util.ClassUtil.isPresent;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/9 19:27
**/
@Slf4j
@Configuration
@ConditionalOnProperty(value = "spring.application.log-ready-info", havingValue = "true", matchIfMissing = true)
public class ApplicationReadyInfoAutoConfiguration {
@Configuration
@RequiredArgsConstructor
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
public static class WebInfoPrinter implements ApplicationListener<ApplicationReadyEvent> {
// the application name
@Value(Placeholders.APPLICATION_NAME)
private String appName;
private final ServerProperties properties;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
Environment environment = event.getApplicationContext().getEnvironment();
Ssl ssl = properties.getSsl();
String protocol = (ssl == null || ssl.getKeyStore() == null) ? "http" : "https";
Integer port = properties.getPort();
if (port == null) {
port = 8080;
}
String ip;
int timeoutSeconds = EnvironmentUtil.fetchLocalIpTimeoutSeconds(environment);
try {
ip = Inets.fetchLocalIp(timeoutSeconds);
} catch (FetchException e) {
log.debug("Cannot fetch local ip in " + timeoutSeconds + " seconds");
ip = null;
}
String[] activeProfiles = getActiveProfiles(environment);
// application base info
StringBuilder s = new StringBuilder()
.append("\n----------------------------------------------------------------\n")
.append("\tApplication ").append(appName).append(" is running! Access URLs:\n")
.append("\tLocal:\t\t\t").append(protocol).append("://localhost:").append(port).append("\n");
if (ip != null) {
s.append("\tExternal:\t\t").append(protocol).append("://").append(ip)
.append(":").append(port).append("\n");
String swaggerPageLocation = "classpath:META-INF/resources/swagger-ui.html";
if (Seq.of(activeProfiles).contains("swagger") && Resources.exists(swaggerPageLocation)) {
s.append("\tSwaggerUI:\t\t").append(protocol).append("://").append(ip)
.append(":").append(port).append("/swagger-ui.html\n");
}
}
s.append("\tProfile(s):\t\t").append(Arrays.toString(activeProfiles)).append("\n");
s.append("----------------------------------------------------------------");
// config server info
s.append(getConfigServerStatus(event));
log.info(s.toString());
}
}
@Configuration
@ConditionalOnNotWebApplication
public static class InfoPrinter implements ApplicationListener<ApplicationReadyEvent> {
// the application name
@Value(Placeholders.APPLICATION_NAME)
private String appName;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
String[] activeProfiles = getActiveProfiles(event.getApplicationContext().getEnvironment());
// application base info
String info = "\n----------------------------------------------------------------\n" +
"\tApplication " + appName + " is running!\n" +
"\tProfile(s):\t" + Arrays.toString(activeProfiles) + "\n" +
"----------------------------------------------------------------";
// config server info
info += getConfigServerStatus(event);
log.info(info);
}
}
/**
* Log config server info on condition that dependency is present.
*
* @param event event when this application is ready.
*/
private static String getConfigServerStatus(ApplicationReadyEvent event) {
val environment = event.getApplicationContext().getEnvironment();
val classLoader = event.getApplicationContext().getClassLoader();
if (isPresent("org.springframework.cloud.config.client.ConfigClientProperties", classLoader)) {
val status = Optional.ofNullable(environment.getProperty("configserver.status"));
return "\n----------------------------------------------------------------\n" +
"\tConfig Server:\t" + status.orElse("Not found or not setup for this application!") + "\n" +
"----------------------------------------------------------------";
}
return "";
}
}

View File

@ -0,0 +1,318 @@
package cn.axzo.framework.autoconfigure.env;
import cn.axzo.framework.boot.EnvironmentUtil;
import cn.axzo.framework.boot.logging.LoggingConfigFixer;
import cn.axzo.framework.core.util.MapUtil;
import org.jooq.lambda.Seq;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.LogFile;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static jodd.util.StringPool.COMMA;
import static org.springframework.util.ClassUtils.isPresent;
/**
* 覆盖不合理的配置或添加默认配置
*
* @author liyong.tian
* @since 2019/10/13 10:21
*/
public class BuildInConfigOverridePostProcessor implements EnvironmentPostProcessor, Ordered {
/**
* The default order for the processor.
*/
public static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE;
private static final String BUILD_IN_CONFIG_OVERRIDE_PS_NAME = "buildInConfigOverride";
private int order = DEFAULT_ORDER;
private LoggingConfigFixer loggingConfigFixer = new LoggingConfigFixer();
private ConfigurableEnvironment environment;
private ClassLoader classLoader;
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
this.environment = environment;
this.classLoader = application.getClassLoader();
boolean isSpringCloudContext = EnvironmentUtil.isSpringCloudContext(environment);
Map<String, Object> map = new HashMap<>();
// 设置日志文件名
overrideLoggingConfig(map, isSpringCloudContext);
// don't listen to events in a spring cloud context
if (!isSpringCloudContext) {
// 在ribbon环境中当Apache HttpClient和OKHttp依赖都存在则使用OKHttp
overrideRibbonOKHttp(map);
// 在feign环境中当Apache HttpClient和OKHttp依赖都存在则使用OKHttp
overrideFeignOKHttp(map);
// 修复thymeleaf默认的配置在thymeleaf3环境下的告警
overrideThymeleafProperties(map);
// 给management的context-path设置默认值
overrideManagementServerProperties(map);
// 自动配置CGLIB代理
overrideCglibProxy(map);
// 增加json格式的mime-type
overrideCompressionMimeTypes(map);
// 设置EurekaServer的环境名
overrideEurekaServerEnvironment(map);
// 默认禁用favicon
overrideMvcFavicon(map);
// 设置config server审计端口的应用名
overrideConfigServerHealthRepositories(map);
// 自动配置mvc中的trace支持
overrideServerErrorTraceSupport(map);
// 默认不开启metrics的过滤器
disableMetricsFilterIfAbsent(map);
}
map = MapUtil.removeNulls(map);
if (MapUtil.isNotEmpty(map)) {
MapPropertySource propertySource = new MapPropertySource(BUILD_IN_CONFIG_OVERRIDE_PS_NAME, map);
environment.getPropertySources().addFirst(propertySource);
}
}
private void disableMetricsFilterIfAbsent(Map<String, Object> map) {
String key = "endpoints.metrics.filter.enabled";
if (isEnvContainsNone(key)) {
map.put(key, false);
}
}
private void overrideServerErrorTraceSupport(Map<String, Object> map) {
if (isWebApplication()) {
String key = "server.error.includeStacktrace";
String relaxedKey = "server.error.include-stacktrace";
if (isEnvContainsNone(key, relaxedKey)) {
map.put(key, ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM);
}
}
}
private void overrideConfigServerHealthRepositories(Map<String, Object> map) {
if (isConfigServerHealthPresent()) {
String key = "spring.cloud.config.server.health.repositories.application.name";
if (isEnvContainsNone(key)) {
map.put(key, "application");
}
}
}
private void overrideMvcFavicon(Map<String, Object> map) {
if (isWebApplication()) {
String key = "spring.mvc.favicon.enabled";
if (isEnvContainsNone(key, key)) {
map.put(key, "false");
}
}
}
private void overrideEurekaServerEnvironment(Map<String, Object> map) {
if (isEurekaServerPresent()) {
String key = "eureka.environment";
if (isEnvContainsNone(key, key)) {
String[] activeProfiles = environment.getActiveProfiles();
if (activeProfiles == null || activeProfiles.length == 0) {
map.put(key, "default");
} else {
map.put(key, Seq.of(activeProfiles).toString(COMMA));
}
}
}
}
private void overrideLoggingConfig(Map<String, Object> map, boolean isSpringCloudContext) {
String pathKey = LogFile.FILE_PATH_PROPERTY;
String fileKey = LogFile.FILE_NAME_PROPERTY;
if (isEnvContainsNone(pathKey, fileKey)) {
return;
}
// path
String loggingPath;
if (isEnvContainsNone(pathKey, pathKey)) {
loggingPath = "logs";
} else {
loggingPath = environment.getProperty(pathKey);
}
// file
String loggingFile;
if (isEnvContainsNone(fileKey, fileKey)) {
String appName = environment.getProperty("spring.application.name");
if (appName != null) {
loggingFile = appName + ".log";
} else {
loggingFile = isSpringCloudContext ? "bootstrap.log" : "spring.log";
}
} else {
loggingFile = environment.getProperty(fileKey);
}
// fix file
loggingFile = loggingConfigFixer.fixLoggingFile(loggingPath, loggingFile).orElse(loggingFile);
map.put(pathKey, loggingPath);
map.put(fileKey, loggingFile);
}
private void overrideCompressionMimeTypes(Map<String, Object> map) {
String key = "server.compression.mimeTypes";
String relaxedKey = "server.compression.mime-types";
if (isEnvContainsNone(key, relaxedKey)) {
String[] mimeTypes = {
"text/html", "text/xml", "text/plain", "text/css", "text/javascript",
"application/javascript", "application/json"
};
map.put(key, mimeTypes);
map.put(relaxedKey, mimeTypes);
}
}
private void overrideCglibProxy(Map<String, Object> map) {
String key = "spring.aop.proxyTargetClass";
String relaxedKey = "spring.aop.proxy-target-class";
if (isEnvContainsNone(key, relaxedKey)) {
map.put(key, true);
map.put(relaxedKey, true);
}
}
private void overrideManagementServerProperties(Map<String, Object> map) {
if (isActuatorPresent()) {
String key = "management.contextPath";
String relaxedKey = "management.context-path";
if (isEnvContainsNone(key, relaxedKey)) {
map.put(key, "/management");
map.put(relaxedKey, "/management");
}
}
}
private void overrideThymeleafProperties(Map<String, Object> map) {
if (isThymeleaf3Present()) {
String key = "spring.thymeleaf.mode";
// if (isEnvContainsNone(key, key)) {
// map.put(key, TemplateMode.HTML.name());
// }
key = "spring.thymeleaf.checkTemplateLocation";
String relaxedKey = "spring.thymeleaf.check-template-location";
if (isEnvContainsNone(key, relaxedKey)) {
map.put(key, false);
map.put(relaxedKey, false);
}
}
}
private void overrideRibbonOKHttp(Map<String, Object> map) {
if (isRibbonOKHttpPresent()) {
String key = "ribbon.httpclient.enabled";
if (isEnvContainsNone(key, key)) {
map.put(key, false);
}
key = "ribbon.okhttp.enabled";
if (isEnvContainsNone(key, key)) {
map.put(key, true);
}
}
}
private void overrideFeignOKHttp(Map<String, Object> map) {
if (isFeignOKHttpPresent()) {
String key = "feign.httpclient.enabled";
if (isEnvContainsNone(key, key)) {
map.put(key, false);
}
key = "feign.okhttp.enabled";
if (isEnvContainsNone(key, key)) {
map.put(key, true);
}
}
}
private boolean isConfigServerHealthPresent() {
Boolean healthEnabled = environment.getProperty("spring.cloud.config.server.health.enabled", Boolean.class);
return isPresent("org.springframework.cloud.config.server.config.ConfigServerHealthIndicator", classLoader)
&& (healthEnabled == null || healthEnabled);
}
private boolean isWebApplication() {
return isPresent("org.springframework.web.context.WebApplicationContext", classLoader)
&& isPresent("org.springframework.web.context.support.GenericWebApplicationContext", classLoader);
}
private boolean isEurekaServerPresent() {
return isPresent("org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap", classLoader);
}
private boolean isActuatorPresent() {
return isPresent("org.springframework.boot.actuate.autoconfigure.ManagementServerProperties", classLoader);
}
private boolean isThymeleaf3Present() {
return isPresent("org.thymeleaf.templatemode.TemplateMode", classLoader);
}
private boolean isRibbonOKHttpPresent() {
return isPresent("org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration", classLoader)
&& isPresent("okhttp3.OkHttpClient", classLoader);
}
private boolean isFeignOKHttpPresent() {
return isPresent("org.springframework.cloud.netflix.feign.ribbon.OkHttpFeignLoadBalancedConfiguration", classLoader)
&& isPresent("feign.okhttp.OkHttpClient", classLoader)
&& isPresent("okhttp3.OkHttpClient", classLoader);
}
private boolean isEnvContainsNone(String... keys) {
if (keys.length == 1) {
return !environment.containsProperty(keys[0]);
}
if (keys.length == 2) {
if (Objects.equals(keys[0], keys[1])) {
return !environment.containsProperty(keys[0]);
} else {
return !environment.containsProperty(keys[0]) && !environment.containsProperty(keys[1]);
}
}
return Arrays.stream(keys).noneMatch(key -> environment.containsProperty(key));
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}

View File

@ -0,0 +1,43 @@
package cn.axzo.framework.autoconfigure.jackson;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.core.Ordered;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.util.TimeZone;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_ABSENT;
import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER;
import static com.fasterxml.jackson.databind.DeserializationFeature.*;
import static com.fasterxml.jackson.databind.MapperFeature.PROPAGATE_TRANSIENT_MARKER;
import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/9 19:37
**/
public class JacksonCustomer implements Jackson2ObjectMapperBuilderCustomizer, Ordered {
@Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.serializationInclusion(NON_ABSENT);
builder.timeZone(TimeZone.getDefault());
// disable
builder.featuresToDisable(READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
builder.featuresToDisable(WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
builder.featuresToDisable(ACCEPT_FLOAT_AS_INT);
// enable
builder.featuresToEnable(ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER);
builder.featuresToEnable(ACCEPT_SINGLE_VALUE_AS_ARRAY);
builder.featuresToEnable(PROPAGATE_TRANSIENT_MARKER);
}
@Override
public int getOrder() {
return -1;
}
}

View File

@ -0,0 +1,71 @@
package cn.axzo.framework.autoconfigure.jackson;
import cn.axzo.framework.jackson.datatype.enumstd.EnumStdModule;
import cn.axzo.framework.jackson.datatype.fraction.FractionModule;
import cn.axzo.framework.jackson.datatype.period.PeriodModule;
import cn.axzo.framework.jackson.datatype.string.TrimModule;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hppc.HppcModule;
import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/9 19:39
**/
@Configuration
@ConditionalOnClass({ObjectMapper.class, Jackson2ObjectMapperBuilder.class})
public class JacksonModuleAutoConfiguration {
@Bean
public JacksonCustomer jacksonCustomer() {
return new JacksonCustomer();
}
@ConditionalOnClass(name = "cn.axzo.framework.jackson.datatype.enumstd.cn.axzo.framework.jackson.datatype.enumstd.EnumStdModule")
@Bean
public EnumStdModule enumStdModule() {
return new EnumStdModule();
}
@ConditionalOnClass(name = "cn.axzo.framework.jackson.datatype.fraction.cn.axzo.framework.jackson.datatype.fraction.FractionModule")
@Bean
public FractionModule fractionModule() {
return new FractionModule();
}
@ConditionalOnClass(name = "cn.axzo.framework.jackson.datatype.period.cn.axzo.framework.jackson.datatype.period.PeriodModule")
@Bean
public PeriodModule periodModule() {
return new PeriodModule();
}
@ConditionalOnClass(name = "cn.axzo.framework.jackson.datatype.string.TrimModule")
@Bean
public TrimModule trimModule() {
return new TrimModule();
}
@ConditionalOnClass(name = "com.fasterxml.jackson.module.afterburner.AfterburnerModule")
@Bean
public AfterburnerModule afterburnerModule() {
return new AfterburnerModule();
}
@ConditionalOnClass(name = "com.fasterxml.jackson.datatype.hppc.HppcModule")
@Bean
public HppcModule hppcModule() {
return new HppcModule();
}
@ConditionalOnClass(name = "com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule")
@Bean
public JsonOrgModule jsonOrgModule() {
return new JsonOrgModule();
}
}

View File

@ -0,0 +1,4 @@
/**
* Service layer beans.
*/
package cn.axzo.framework.autoconfigure;

View File

@ -0,0 +1,35 @@
package cn.axzo.framework.autoconfigure.validation;
import lombok.val;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
/**
* @Description JSR-349规范
* @Author liyong.tian
* @Date 2020/9/9 19:42
**/
@Configuration
@ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
@AutoConfigureBefore(ValidationAutoConfiguration.class)
public class MethodValidationAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, Validator validator) {
val processor = ValidationAutoConfiguration.methodValidationPostProcessor(environment, validator, null);
processor.setBeforeExistingAdvisors(true);
return processor;
}
}

View File

@ -0,0 +1,39 @@
package cn.axzo.framework.autoconfigure.validation;
import cn.axzo.framework.context.validation.SpringValidator;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.validation.SmartValidator;
import java.util.Optional;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/9 19:44
**/
@Configuration
@ConditionalOnBean(SmartValidator.class)
@ConditionalOnClass(SpringValidator.class)
@RequiredArgsConstructor
public class SpringValidatorAutoConfiguration {
private final SmartValidator validator;
private final ObjectProvider<ConversionService> conversionServiceProvider;
@Bean
@ConditionalOnMissingBean
public SpringValidator springValidator() {
ConversionService conversionService = Optional.ofNullable(conversionServiceProvider.getIfAvailable())
.orElseGet(DefaultFormattingConversionService::new);
return new SpringValidator(validator, conversionService);
}
}

View File

@ -0,0 +1,34 @@
package cn.axzo.framework.autoconfigure.web;
import cn.axzo.framework.web.servlet.filter.OrderedBadRequestFilter;
import cn.axzo.framework.web.servlet.filter.OrderedTimerFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.Servlet;
/**
* @Description 自定义过滤器
* @Author liyong.tian
* @Date 2020/9/9 19:49
**/
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
public class FilterAutoConfiguration {
@Bean
@ConditionalOnClass(name = "cn.axzo.framework.web.servlet.filter.OrderedTimerFilter")
public OrderedTimerFilter orderedTimerFilter() {
return new OrderedTimerFilter();
}
@Bean
@ConditionalOnClass(name = "cn.axzo.framework.web.servlet.filter.OrderedBadRequestFilter")
public OrderedBadRequestFilter orderedBadRequestFilter() {
return new OrderedBadRequestFilter();
}
}

View File

@ -0,0 +1,64 @@
package cn.axzo.framework.autoconfigure.web;
import cn.axzo.framework.web.page.PageableArgumentResolver;
import cn.axzo.framework.web.page.RestPageProperties;
import cn.axzo.framework.web.page.SortArgumentResolver;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.Servlet;
import java.util.List;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/9 19:51
**/
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, RestPageProperties.class})
public class PageWebAutoConfiguration {
@Bean
@Validated
@ConfigurationProperties("spring.rest.page")
@ConditionalOnMissingBean
public RestPageProperties restPageProperties() {
return new RestPageProperties();
}
@Configuration
@ConditionalOnClass({SortArgumentResolver.class, PageableArgumentResolver.class})
static class PageAndSortConfiguration implements WebMvcConfigurer {
private final RestPageProperties restPageProperties;
public PageAndSortConfiguration(RestPageProperties restPageProperties) {
this.restPageProperties = restPageProperties;
}
@Bean
public SortArgumentResolver sortArgumentResolver() {
return new SortArgumentResolver(restPageProperties);
}
@Bean
public PageableArgumentResolver pageableArgumentResolver() {
return new PageableArgumentResolver(restPageProperties, sortArgumentResolver());
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(sortArgumentResolver());
argumentResolvers.add(pageableArgumentResolver());
}
}
}

View File

@ -0,0 +1,18 @@
package cn.axzo.framework.autoconfigure.web.advice;
import com.fasterxml.jackson.databind.JsonNode;
/**
* @author liyong.tian
* @since 2019/4/16 19:01
*/
public class ApiResultDynamicBodyExtractor implements DynamicBodyExtractor{
@Override
public JsonNode extract(JsonNode body) {
if (body.has("data")) {
return body.get("data");
}
return body;
}
}

View File

@ -0,0 +1,42 @@
package cn.axzo.framework.autoconfigure.web.advice;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.Servlet;
/**
* @author liyong.tian
* @since 2019/4/17 10:17
*/
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
public class BodyAdviceAutoConfiguration {
@Bean
@ConditionalOnClass(name = "cn.axzo.framework.web.servlet.filter.OrderedTimerFilter")
@ConditionalOnProperty(value = "spring.mvc.response.verbose-enabled", havingValue = "true")
public VerboseResultAdvice verboseResultAdvice() {
return new VerboseResultAdvice();
}
@Bean
@ConditionalOnMissingBean
public DynamicBodyExtractor dynamicBodyExtractor() {
return new ApiResultDynamicBodyExtractor();
}
@Bean
@ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(value = "spring.mvc.response.dynamic-enabled", havingValue = "true", matchIfMissing = true)
public DynamicResponseBodyAdvice dynamicResponseBodyAdvice(ObjectMapper objectMapper,
DynamicBodyExtractor dynamicBodyExtractor) {
return new DynamicResponseBodyAdvice(objectMapper, dynamicBodyExtractor);
}
}

View File

@ -0,0 +1,12 @@
package cn.axzo.framework.autoconfigure.web.advice;
import com.fasterxml.jackson.databind.JsonNode;
/**
* @author liyong.tian
* @since 2019/4/16 19:00
*/
public interface DynamicBodyExtractor {
JsonNode extract(JsonNode body);
}

View File

@ -0,0 +1,98 @@
package cn.axzo.framework.autoconfigure.web.advice;
import cn.axzo.framework.jackson.utility.DynamicJSON;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* 动态JSON响应
*
* @author liyong.tian
* @since 2019/4/16 19:04
*/
@SuppressWarnings("WeakerAccess")
@Slf4j
@RestControllerAdvice(annotations = RestController.class)
public class DynamicResponseBodyAdvice implements ResponseBodyAdvice<Object>, Ordered {
private final static String PARAMETER_FIELDS = "fields";
private final ObjectMapper objectMapper;
private final DynamicBodyExtractor extractor;
public DynamicResponseBodyAdvice(ObjectMapper objectMapper, DynamicBodyExtractor extractor) {
log.debug("Add ResponseBodyAdvice: DynamicResponseBodyAdvice");
this.objectMapper = objectMapper;
this.extractor = extractor;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 10;
}
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (!(request instanceof ServletServerHttpRequest)) {
return body;
}
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
String[] fields = servletRequest.getParameterValues(PARAMETER_FIELDS);
if (ArrayUtils.isEmpty(fields)) {
return body;
}
fields = convertIfNecessary(fields);
if (ArrayUtils.isNotEmpty(fields)) {
JsonNode node = objectMapper.valueToTree(body);
DynamicJSON.filter(extractor.extract(node), fields);
return node;
}
return body;
}
private String[] convertIfNecessary(String[] origin) {
String[][] valuesArray = new String[origin.length][];
int length = 0;
for (int i = 0; i < origin.length; i++) {
String[] values = StringUtils.commaDelimitedListToStringArray(origin[i]);
length += values.length;
valuesArray[i] = values;
}
String[] result = new String[length];
int index = 0;
for (String[] values : valuesArray) {
System.arraycopy(values, 0, result, index, values.length);
index += values.length;
}
return result;
}
}

View File

@ -0,0 +1,98 @@
package cn.axzo.framework.autoconfigure.web.advice;
import cn.axzo.framework.domain.web.result.Result;
import cn.axzo.framework.web.servlet.filter.OrderedTimerFilter;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 返回接口中加上一些额外信息
*
* @author liyong.tian
* @since 2019/4/16 19:19
*/
@SuppressWarnings({"WeakerAccess"})
@Slf4j
@RestControllerAdvice(annotations = RestController.class)
public class VerboseResultAdvice implements ResponseBodyAdvice<Object>, Ordered {
private final static String PROPERTY_DURATION = "duration";
public VerboseResultAdvice() {
log.debug("Add ResponseBodyAdvice: ResultAdvice");
}
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
val returnType = methodParameter.getParameterType();
return HttpEntity.class.isAssignableFrom(returnType)
|| Result.class.isAssignableFrom(returnType)
|| ObjectNode.class.isAssignableFrom(returnType);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter,
MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest request,
ServerHttpResponse response) {
if (!(request instanceof ServletServerHttpRequest)) {
return body;
}
if (body instanceof Result) {
Long duration = calcDuration(((ServletServerHttpRequest) request).getServletRequest());
if (duration != null) {
Map<String, Object> map = ((Result) body).toMap();
map.put(PROPERTY_DURATION, duration);
return map;
}
} else if (body instanceof ObjectNode) {
Long duration = calcDuration(((ServletServerHttpRequest) request).getServletRequest());
if (duration != null) {
((ObjectNode) body).put(PROPERTY_DURATION, duration);
}
}
return body;
}
@Nullable
private Long calcDuration(HttpServletRequest servletRequest) {
Long startTime = getStartTime(servletRequest);
if (startTime != null) {
return (System.nanoTime() - startTime) / 1000_000;
}
return null;
}
@Nullable
private Long getStartTime(HttpServletRequest servletRequest) {
Object startTime = servletRequest.getAttribute(OrderedTimerFilter.ATTRIBUTE_START_TIME);
if (startTime instanceof Long) {
return (Long) startTime;
}
return null;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}

View File

@ -0,0 +1,27 @@
package cn.axzo.framework.autoconfigure.web.context;
import cn.axzo.framework.web.servlet.context.RequestMappingHandlerAdapterLazyAwareProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.Servlet;
/**
* @Description
* @Author liyong.tian
* @Date 2020/12/14 17:35
**/
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
public class WebMvcAwareAutoConfiguration {
@Bean
public RequestMappingHandlerAdapterLazyAwareProcessor requestMappingHandlerAdapterLazyAwareProcessor() {
return new RequestMappingHandlerAdapterLazyAwareProcessor();
}
}

View File

@ -0,0 +1,95 @@
package cn.axzo.framework.autoconfigure.web.cors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.CorsRegistration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import javax.servlet.Servlet;
import static java.util.Arrays.stream;
/**
* CORS跨域配置
*
* @author liyong.tian
* @since 2019/5/30 16:50
*/
@Slf4j
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@ConditionalOnProperty(prefix = "spring.mvc.cors", name = "enabled", havingValue = "true")
@EnableConfigurationProperties(CorsProperties.class)
@RequiredArgsConstructor
public class CorsAutoConfiguration extends WebMvcConfigurationSupport {
private final CorsProperties properties;
/**
* 注册CORS过滤器
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsStdRegistration(properties).getCorsConfiguration();
if (!config.getAllowedOrigins().isEmpty()) {
log.debug("Registering CORS filter");
stream(properties.getPatterns()).forEach(pattern -> source.registerCorsConfiguration(pattern, config));
}
return new CorsFilter(source);
}
/**
* 配置CorsInterceptor的CORS参数
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
log.debug("Registering CORS interceptor");
stream(properties.getPatterns()).forEach(pattern ->
registry.addMapping(pattern)
.allowedOrigins(properties.getAllowedOrigins())
.allowedMethods(properties.getAllowedMethods())
.allowedHeaders(properties.getAllowedHeaders())
.exposedHeaders(properties.getExposedHeaders())
.allowCredentials(properties.getAllowCredentials())
.maxAge(properties.getMaxAge())
);
}
static class CorsStdRegistration extends CorsRegistration {
/**
* Create a new {@link CorsRegistration} that allows all origins, headers, and
* credentials for {@code GET}, {@code HEAD}, and {@code POST} requests with
* max age set to 1800 seconds (30 minutes) for the specified path.
*
* @param properties cors配置属性
*/
public CorsStdRegistration(CorsProperties properties) {
super(CorsConfiguration.ALL);
super.allowCredentials(properties.getAllowCredentials());
super.allowedHeaders(properties.getAllowedHeaders());
super.allowedMethods(properties.getAllowedMethods());
super.allowedOrigins(properties.getAllowedOrigins());
super.exposedHeaders(properties.getExposedHeaders());
super.maxAge(properties.getMaxAge());
}
@Override
public CorsConfiguration getCorsConfiguration() {
return super.getCorsConfiguration();
}
}
}

View File

@ -0,0 +1,94 @@
package cn.axzo.framework.autoconfigure.web.cors;
import jodd.util.StringUtil;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.stream.Stream;
import static org.springframework.http.HttpMethod.*;
import static org.springframework.web.cors.CorsConfiguration.ALL;
/**
* CORS跨域配置
*
* @author liyong.tian
* @since 2019/5/30 16:01
*/
@Data
@Validated
@ConfigurationProperties("spring.mvc.cors")
public class CorsProperties {
private boolean enabled;
/**
* the path that the CORS configuration should apply to;
* exact path mapping URIs (such as {@code "/admin"}) are supported as well
* as Ant-style path patterns (such as {@code "/admin/**"}). When not set,
* defaults to '/**'.
*/
@NotEmpty
private String[] patterns = {"/**"};
/**
* Comma-separated list of origins to allow. '*' allows all origins. When not set,
* defaults to '*'.
*/
@NotNull
private String[] allowedOrigins = new String[]{ALL};
/**
* Comma-separated list of methods to allow. '*' allows all methods. When not set,
* defaults to GET, HEAD, POST.
*/
@NotNull
private String[] allowedMethods = {GET.name(), HEAD.name(), POST.name()};
/**
* Comma-separated list of headers to allow in a request. '*' allows all headers. When not set,
* defaults to '*'.
*/
@NotNull
private String[] allowedHeaders = {ALL};
/**
* Comma-separated list of headers to include in a response.
*/
@NotNull
private String[] exposedHeaders = {};
/**
* Set whether credentials are supported. When not set, defaults to 'true'.
*/
@NotNull
private Boolean allowCredentials = true;
/**
* How long, in seconds, the response from a pre-flight request can be cached by clients. When not set,
* defaults to 1800.
*/
@NotNull
@Min(1)
private Long maxAge = 1800L;
public String[] getAllowedOrigins() {
return Stream.of(allowedOrigins).filter(StringUtil::isNotBlank).toArray(String[]::new);
}
public String[] getAllowedMethods() {
return Stream.of(allowedMethods).filter(StringUtil::isNotBlank).map(String::toUpperCase).toArray(String[]::new);
}
public String[] getAllowedHeaders() {
return Stream.of(allowedHeaders).filter(StringUtil::isNotBlank).toArray(String[]::new);
}
public String[] getExposedHeaders() {
return Stream.of(exposedHeaders).filter(StringUtil::isNotBlank).toArray(String[]::new);
}
}

View File

@ -0,0 +1,187 @@
package cn.axzo.framework.autoconfigure.web.exception;
import cn.axzo.framework.autoconfigure.web.exception.handler.ExceptionResultHandler;
import cn.axzo.framework.autoconfigure.web.exception.handler.internal.*;
import cn.axzo.framework.autoconfigure.web.exception.resolver.HttpStatusResolver;
import cn.axzo.framework.autoconfigure.web.exception.resolver.internal.FileTooLargeExceptionHttpStatusResolver;
import cn.axzo.framework.autoconfigure.web.exception.resolver.internal.RequestRejectedExceptionHttpStatusResolver;
import cn.axzo.framework.autoconfigure.web.exception.support.GlobalErrorController;
import cn.axzo.framework.autoconfigure.web.exception.support.GlobalExceptionHandler;
import cn.axzo.framework.domain.web.result.Result;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.servlet.Servlet;
import java.util.List;
import static org.springframework.boot.autoconfigure.condition.SearchStrategy.CURRENT;
/**
* @author liyong.tian
* @since 2017/1/17
*/
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@AutoConfigureBefore(ErrorMvcAutoConfiguration.class)
@EnableConfigurationProperties(RespErrorCodeMappingProperties.class)
public class ExceptionHandlerAutoConfiguration implements WebMvcConfigurer {
@Configuration
public static class ExceptionResultHandlerConfiguration {
private final RespErrorCodeMappingProperties properties;
public ExceptionResultHandlerConfiguration(RespErrorCodeMappingProperties properties) {
this.properties = properties;
}
/**
* 异常的标准处理方式
*/
@Bean
public StandardExceptionResultHandler standardErrorControllerCustomer() {
return new StandardExceptionResultHandler(properties);
}
@Bean
@ConditionalOnClass(name = "org.springframework.dao.ConcurrencyFailureException")
@ConditionalOnMissingBean(name = "concurrencyFailureExceptionResultHandler")
public ConcurrencyFailureExceptionResultHandler concurrencyFailureExceptionResultHandler() {
return new ConcurrencyFailureExceptionResultHandler(properties);
}
@Bean
@ConditionalOnClass(name = "org.springframework.security.access.AccessDeniedException")
@ConditionalOnMissingBean(name = "accessDeniedExceptionResultHandler")
public AccessDeniedExceptionResultHandler accessDeniedExceptionResultHandler() {
return new AccessDeniedExceptionResultHandler(properties);
}
@Bean
@ConditionalOnMissingBean(name = "pageRequestExceptionResultHandler")
public PageRequestExceptionResultHandler pageRequestExceptionResultHandler() {
return new PageRequestExceptionResultHandler(properties);
}
@Bean
@ConditionalOnClass(name = "org.springframework.web.client.HttpClientErrorException")
@ConditionalOnMissingBean(name = "httpClientErrorExceptionResultHandler")
public HttpClientErrorExceptionResultHandler httpClientErrorExceptionResultHandler() {
return new HttpClientErrorExceptionResultHandler(properties);
}
@Bean
@ConditionalOnClass(name = "cn.axzo.framework.context.validation.SpringValidatorException")
@ConditionalOnMissingBean(name = "springValidatorExceptionResultHandler")
public SpringValidatorExceptionResultHandler springValidatorExceptionResultHandler() {
return new SpringValidatorExceptionResultHandler(properties);
}
@Bean
@ConditionalOnClass(name = "org.springframework.security.web.firewall.RequestRejectedException")
@ConditionalOnMissingBean(name = "requestRejectedExceptionResultHandler")
public RequestRejectedExceptionResultHandler requestRejectedExceptionResultHandler() {
return new RequestRejectedExceptionResultHandler(properties);
}
@Bean
@ConditionalOnClass(name = "javax.validation.ValidationException")
@ConditionalOnMissingBean(name = "validationExceptionResultHandler")
public ValidationExceptionResultHandler validationExceptionResultHandler() {
return new ValidationExceptionResultHandler(properties);
}
}
@Configuration
@ConditionalOnClass({OAuth2Exception.class, WebResponseExceptionTranslator.class})
public static class OAuth2ExceptionResultHandlerConfiguration {
private final RespErrorCodeMappingProperties properties;
public OAuth2ExceptionResultHandlerConfiguration(RespErrorCodeMappingProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
public WebResponseExceptionTranslator webResponseExceptionTranslator() {
return new DefaultWebResponseExceptionTranslator();
}
@Bean
@ConditionalOnMissingBean
public OAuth2ExceptionResultHandler oAuth2ExceptionReturnHandler(WebResponseExceptionTranslator translator) {
return new OAuth2ExceptionResultHandler(properties, translator);
}
@Bean
@ConditionalOnMissingBean
public ClientRegistrationExceptionReturnHandler clientRegistrationExceptionReturnHandler(
WebResponseExceptionTranslator translator) {
return new ClientRegistrationExceptionReturnHandler(properties, translator);
}
}
@Configuration
public static class ErrorControllerConfiguration {
private final ServerProperties serverProperties;
private final ErrorAttributes errorAttributes;
private final List<ExceptionResultHandler<? extends Throwable, ? extends Result>> handlers;
public ErrorControllerConfiguration(ServerProperties serverProperties, ErrorAttributes errorAttributes,
List<ExceptionResultHandler<? extends Throwable, ? extends Result>> handlers) {
this.serverProperties = serverProperties;
this.errorAttributes = errorAttributes;
this.handlers = handlers;
}
/**
* 全局异常捕获
*/
@Bean
@ConditionalOnMissingBean(value = {ErrorController.class, ResponseEntityExceptionHandler.class}, search = CURRENT)
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler(handlers, errorAttributes, serverProperties);
}
/**
* HTTP请求异常捕获
*/
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = CURRENT)
public GlobalErrorController globalErrorController(List<HttpStatusResolver> resolvers) {
return new GlobalErrorController(errorAttributes, serverProperties, resolvers);
}
@Bean
@ConditionalOnMissingBean(name = "fileTooLargeExceptionHttpStatusResolver")
public FileTooLargeExceptionHttpStatusResolver fileTooLargeExceptionHttpStatusResolver() {
return new FileTooLargeExceptionHttpStatusResolver();
}
@Bean
@ConditionalOnMissingBean(name = "requestRejectedExceptionHttpStatusResolver")
public RequestRejectedExceptionHttpStatusResolver requestRejectedExceptionHttpStatusResolver() {
return new RequestRejectedExceptionHttpStatusResolver();
}
}
}

View File

@ -0,0 +1,80 @@
package cn.axzo.framework.autoconfigure.web.exception;
import cn.axzo.framework.domain.web.code.BaseCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import java.util.Map;
import static com.google.common.collect.Maps.newHashMap;
import static org.springframework.http.HttpStatus.*;
/**
* 配置错误响应码到HTTP状态码的映射关系
* <p>
* 默认情况下基础错误响应码会映射到相同的HTTP状态码非基础错误响应码都映射到451UNAVAILABLE_FOR_LEGAL_REASONS
* 可配置可自定义映射关系用来满足自己的业务需求
* <p>
* 几种常见的配置需求
* <pre>
* // 所有的错误响应码都映射到HTTP状态码200即HTTP永远返回成功只需关注错误响应码
* resp.error-code.base-mapping.*=ok
* resp.error-code.non-base-mapping.*=ok
* </pre>
* <pre>
* // 所有的非基础错误响应码都映射到HTTP状态码200而基础错误响应码仍然映射到相同的HTTP状态码
* resp.error-code.non-base-mapping.*=ok
* </pre>
*
* @author liyong.tian
* @since 2019/4/15 16:50
*/
@ToString
@Validated
@ConfigurationProperties(prefix = "resp.error-code")
public class RespErrorCodeMappingProperties {
private static final Map<String, HttpStatus> DEFAULT_BASE_MAPPING = newHashMap();
private static final Map<String, HttpStatus> DEFAULT_NON_BASE_MAPPING = newHashMap();
static {
DEFAULT_BASE_MAPPING.put(BaseCode.BAD_REQUEST.getRespCode(), BAD_REQUEST);
DEFAULT_BASE_MAPPING.put(BaseCode.FORBIDDEN.getRespCode(), FORBIDDEN);
DEFAULT_BASE_MAPPING.put(BaseCode.NOT_FOUND.getRespCode(), NOT_FOUND);
DEFAULT_BASE_MAPPING.put(BaseCode.SERVER_ERROR.getRespCode(), INTERNAL_SERVER_ERROR);
DEFAULT_BASE_MAPPING.put(BaseCode.SERVICE_UNAVAILABLE.getRespCode(), SERVICE_UNAVAILABLE);
DEFAULT_BASE_MAPPING.put(BaseCode.UNAUTHORIZED.getRespCode(), UNAUTHORIZED);
DEFAULT_BASE_MAPPING.put(BaseCode.NOT_ACCEPTABLE.getRespCode(), NOT_ACCEPTABLE);
DEFAULT_BASE_MAPPING.put(BaseCode.CONFLICT.getRespCode(), CONFLICT);
DEFAULT_BASE_MAPPING.put(BaseCode.PAYLOAD_TOO_LARGE.getRespCode(), PAYLOAD_TOO_LARGE);
DEFAULT_BASE_MAPPING.put(BaseCode.UNSUPPORTED_MEDIA_TYPE.getRespCode(), UNSUPPORTED_MEDIA_TYPE);
DEFAULT_BASE_MAPPING.put(BaseCode.UNAVAILABLE_FOR_LEGAL_REASONS.getRespCode(), UNAVAILABLE_FOR_LEGAL_REASONS);
DEFAULT_BASE_MAPPING.put(BaseCode.METHOD_NOT_ALLOWED.getRespCode(), METHOD_NOT_ALLOWED);
DEFAULT_NON_BASE_MAPPING.put("*", UNAVAILABLE_FOR_LEGAL_REASONS);
}
// 基础错误响应码 -> HTTP状态码
@NotNull
@Setter
@Getter
private Map<String, HttpStatus> baseMapping = DEFAULT_BASE_MAPPING;
// 非基础错误响应码 -> HTTP状态码
@NotNull
@Setter
@Getter
private Map<String, HttpStatus> nonBaseMapping = DEFAULT_NON_BASE_MAPPING;
// 错误码在mapping中无法匹配时映射的HTTP状态码
@NotNull
@Setter
@Getter
private HttpStatus fallbackHttpStatus = INTERNAL_SERVER_ERROR;
}

View File

@ -0,0 +1,144 @@
package cn.axzo.framework.autoconfigure.web.exception.handler;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.code.IRespCode;
import cn.axzo.framework.domain.web.result.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import static cn.axzo.framework.core.Constants.API_MARKER;
import static cn.axzo.framework.domain.web.code.BaseCode.SERVER_ERROR;
import static cn.axzo.framework.domain.web.result.ApiResult.err;
import static jodd.util.StringPool.ASTERISK;
import static org.springframework.http.HttpHeaders.CACHE_CONTROL;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/9 20:14
**/
@Slf4j
public abstract class AbstractExceptionApiResultHandler<T extends Throwable> implements ExceptionApiResultHandler<T>, Ordered {
// 基础错误响应码 -> HTTP状态码
private final Map<String, HttpStatus> baseMapping;
// 非基础错误响应码 -> HTTP状态码
private final Map<String, HttpStatus> nonBaseMapping;
// 错误码在mapping中无法匹配时映射的HTTP状态码
private final HttpStatus fallbackStatus;
public AbstractExceptionApiResultHandler(RespErrorCodeMappingProperties properties) {
this.baseMapping = properties.getBaseMapping();
this.nonBaseMapping = properties.getNonBaseMapping();
this.fallbackStatus = properties.getFallbackHttpStatus();
}
@Override
public ApiResult<?> handle(HttpServletRequest request, HttpServletResponse response, T ex, Map<String, Object> attributes) {
response.setHeader(CACHE_CONTROL, "no-store");
// 1.获取异常源
T error = getRealCause(ex);
String errorMsg = error.getMessage();
// 2.获取code
IRespCode respCode = decode(error, getFallbackCode());
final String code = respCode.getRespCode();
// 3.映射HTTP状态码
HttpStatus status = mappingHttpStatus(code, ex);
response.setStatus(status.value());
// 4.获取message
final String message;
if (status.is5xxServerError()) {
message = getMessage(respCode, attributes, ex);
} else {
message = getMessage(respCode, ex);
}
// 5.打印日志
log(status, request, errorMsg, error);
// 6.返回body
return err(code, message);
}
protected T getRealCause(T ex) {
return ex;
}
protected IRespCode getFallbackCode() {
return Stream.of(BaseCode.class.getEnumConstants())
.filter(code -> Objects.equals(code.getStatus(), fallbackStatus.value()))
.findFirst()
.orElse(SERVER_ERROR);
}
protected String getMessage(IRespCode respCode, Map<String, Object> attributes, T ex) {
Object trace = attributes.get("trace");
if (trace != null) {
return trace.toString();
} else {
return getMessage(respCode, ex);
}
}
protected String getMessage(IRespCode respCode, T ex) {
return respCode.getMessage();
}
protected abstract IRespCode decode(T ex, IRespCode fallbackCode);
protected HttpStatus mappingHttpStatus(String code, T ex) {
return Stream.of(BaseCode.class.getEnumConstants())
.filter(baseCode -> Objects.equals(baseCode.getRespCode(), code))
.findFirst()
.map(baseCode -> {
if (baseMapping.containsKey(ASTERISK)) {
return baseMapping.get(ASTERISK);
}
if(baseMapping.containsKey(code)) {
return baseMapping.get(code);
}
return fallbackStatus;
}).orElseGet(() -> {
if (nonBaseMapping.containsKey(ASTERISK)) {
return nonBaseMapping.get(ASTERISK);
}
if (nonBaseMapping.containsKey(code)) {
return nonBaseMapping.get(code);
}
return fallbackStatus;
});
}
protected void log(HttpStatus status, HttpServletRequest request, String errorMsg, Throwable error) {
if (status.is5xxServerError()) {
log.error(API_MARKER, "Exception occurred: " + errorMsg + ". [URL=" + request.getRequestURI() + "]", error);
} else {
log.warn(API_MARKER, errorMsg + ". [URL=" + request.getRequestURI() + "]");
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public Class<?> getSuperClass() {
return AbstractExceptionApiResultHandler.class;
}
}

View File

@ -0,0 +1,16 @@
package cn.axzo.framework.autoconfigure.web.exception.handler;
import cn.axzo.framework.domain.web.result.ApiResult;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/9 20:13
**/
public interface ExceptionApiResultHandler<T extends Throwable> extends ExceptionResultHandler<T, ApiResult<?>>{
@Override
default Class<?> getSuperClass() {
return ExceptionApiResultHandler.class;
}
}

View File

@ -0,0 +1,30 @@
package cn.axzo.framework.autoconfigure.web.exception.handler;
import cn.axzo.framework.core.util.ReflectUtil;
import cn.axzo.framework.domain.web.result.Result;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/9 20:12
**/
public interface ExceptionResultHandler<T extends Throwable, R extends Result> {
R handle(HttpServletRequest request, HttpServletResponse response, T e, Map<String, Object> attributes);
default Class<T> getExceptionClass() {
return ReflectUtil.getSuperGenericType(getClass(), getSuperClass());
}
default Class<?> getSuperClass() {
return ExceptionResultHandler.class;
}
default boolean isRecursive() {
return false;
}
}

View File

@ -0,0 +1,24 @@
package cn.axzo.framework.autoconfigure.web.exception.handler.internal;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.code.IRespCode;
import org.springframework.security.access.AccessDeniedException;
/**
* @Description 拒绝访问异常结果
* @Author liyong.tian
* @Date 2020/9/10 10:27
**/
public class AccessDeniedExceptionResultHandler extends AbstractExceptionApiResultHandler<AccessDeniedException> {
public AccessDeniedExceptionResultHandler(RespErrorCodeMappingProperties properties) {
super(properties);
}
@Override
protected IRespCode decode(AccessDeniedException e, IRespCode fallbackCode) {
return BaseCode.FORBIDDEN;
}
}

View File

@ -0,0 +1,44 @@
package cn.axzo.framework.autoconfigure.web.exception.handler.internal;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler;
import cn.axzo.framework.core.InternalException;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.code.IRespCode;
import cn.axzo.framework.web.servlet.security.oauth.OAuth2Util;
import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
/**
* @Description 客户端注册异常返回
* @Author liyong.tian
* @Date 2020/9/10 10:29
**/
public class ClientRegistrationExceptionReturnHandler extends AbstractExceptionApiResultHandler<ClientRegistrationException> {
private final WebResponseExceptionTranslator exceptionTranslator;
private BadClientCredentialsException exception = new BadClientCredentialsException();
public ClientRegistrationExceptionReturnHandler(RespErrorCodeMappingProperties properties,
WebResponseExceptionTranslator exceptionTranslator) {
super(properties);
this.exceptionTranslator = exceptionTranslator;
}
@Override
protected IRespCode decode(ClientRegistrationException e, IRespCode fallbackCode) {
return BaseCode.parse(exception.getHttpErrorCode());
}
@Override
protected String getMessage(IRespCode respCode, ClientRegistrationException e) {
try {
return OAuth2Util.getMessage((OAuth2Exception) exceptionTranslator.translate(exception).getBody());
} catch (Exception ex) {
throw new InternalException(ex);
}
}
}

View File

@ -0,0 +1,24 @@
package cn.axzo.framework.autoconfigure.web.exception.handler.internal;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.code.IRespCode;
import org.springframework.dao.ConcurrencyFailureException;
/**
* @Description 并发故障异常
* @Author liyong.tian
* @Date 2020/9/10 10:31
**/
public class ConcurrencyFailureExceptionResultHandler extends AbstractExceptionApiResultHandler<ConcurrencyFailureException> {
public ConcurrencyFailureExceptionResultHandler(RespErrorCodeMappingProperties properties) {
super(properties);
}
@Override
protected IRespCode decode(ConcurrencyFailureException e, IRespCode fallbackCode) {
return BaseCode.CONFLICT;
}
}

View File

@ -0,0 +1,24 @@
package cn.axzo.framework.autoconfigure.web.exception.handler.internal;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.code.IRespCode;
import org.springframework.web.client.HttpClientErrorException;
/**
* @Description HttpClient错误异常
* @Author liyong.tian
* @Date 2020/9/10 10:33
**/
public class HttpClientErrorExceptionResultHandler extends AbstractExceptionApiResultHandler<HttpClientErrorException> {
public HttpClientErrorExceptionResultHandler(RespErrorCodeMappingProperties properties) {
super(properties);
}
@Override
protected IRespCode decode(HttpClientErrorException e, IRespCode fallbackCode) {
return BaseCode.parse(e.getRawStatusCode());
}
}

View File

@ -0,0 +1,63 @@
package cn.axzo.framework.autoconfigure.web.exception.handler.internal;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler;
import cn.axzo.framework.core.InternalException;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.code.IRespCode;
import cn.axzo.framework.web.servlet.security.oauth.OAuth2Util;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import static org.springframework.security.oauth2.common.exceptions.OAuth2Exception.INVALID_GRANT;
import static org.springframework.security.oauth2.common.exceptions.OAuth2Exception.INVALID_TOKEN;
/**
* OAuth2异常
*
* @author liyong.tian
* @since 2019/4/15 17:54
*/
public class OAuth2ExceptionResultHandler extends AbstractExceptionApiResultHandler<OAuth2Exception> {
private final WebResponseExceptionTranslator exceptionTranslator;
public OAuth2ExceptionResultHandler(RespErrorCodeMappingProperties properties,
WebResponseExceptionTranslator exceptionTranslator) {
super(properties);
this.exceptionTranslator = exceptionTranslator;
}
@Override
protected IRespCode decode(OAuth2Exception e, IRespCode fallbackCode) {
try {
e = (OAuth2Exception) exceptionTranslator.translate(e).getBody();
return BaseCode.parse(e.getHttpErrorCode());
} catch (Exception ex) {
throw new InternalException(ex);
}
}
@Override
protected String getMessage(IRespCode respCode, OAuth2Exception e) {
try {
e = (OAuth2Exception) exceptionTranslator.translate(e).getBody();
String errorCode = e.getOAuth2ErrorCode();
switch (errorCode) {
case INVALID_GRANT:
return "用户名或密码错误";
case INVALID_TOKEN:
return "不合法的Token";
default:
return OAuth2Util.getMessage(e);
}
} catch (Exception ex) {
throw new InternalException(ex);
}
}
@Override
public boolean isRecursive() {
return true;
}
}

View File

@ -0,0 +1,29 @@
package cn.axzo.framework.autoconfigure.web.exception.handler.internal;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler;
import cn.axzo.framework.domain.page.PageRequestException;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.code.IRespCode;
/**
* @Description 分页请求异常
* @Author liyong.tian
* @Date 2020/9/10 10:37
**/
public class PageRequestExceptionResultHandler extends AbstractExceptionApiResultHandler<PageRequestException> {
public PageRequestExceptionResultHandler(RespErrorCodeMappingProperties properties) {
super(properties);
}
@Override
protected IRespCode decode(PageRequestException e, IRespCode fallbackCode) {
return BaseCode.BAD_REQUEST;
}
@Override
protected String getMessage(IRespCode respCode, PageRequestException ex) {
return ex.getMessage();
}
}

View File

@ -0,0 +1,29 @@
package cn.axzo.framework.autoconfigure.web.exception.handler.internal;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.code.IRespCode;
import org.springframework.security.web.firewall.RequestRejectedException;
/**
* @Description 请求拒绝异常
* @Author liyong.tian
* @Date 2020/9/10 10:39
**/
public class RequestRejectedExceptionResultHandler extends AbstractExceptionApiResultHandler<RequestRejectedException> {
public RequestRejectedExceptionResultHandler(RespErrorCodeMappingProperties properties) {
super(properties);
}
@Override
protected IRespCode decode(RequestRejectedException ex, IRespCode fallbackCode) {
return BaseCode.BAD_REQUEST;
}
@Override
protected String getMessage(IRespCode respCode, RequestRejectedException ex) {
return "非法的URL";
}
}

View File

@ -0,0 +1,38 @@
package cn.axzo.framework.autoconfigure.web.exception.handler.internal;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.code.IRespCode;
import cn.axzo.framework.context.validation.SpringValidatorException;
import org.springframework.validation.FieldError;
import static java.lang.String.format;
import static jodd.util.StringUtil.isNotBlank;
/**
* @Description Spring校验异常
* @Author liyong.tian
* @Date 2020/9/10 10:41
**/
public class SpringValidatorExceptionResultHandler extends AbstractExceptionApiResultHandler<SpringValidatorException> {
public SpringValidatorExceptionResultHandler(RespErrorCodeMappingProperties properties) {
super(properties);
}
@Override
protected IRespCode decode(SpringValidatorException ex, IRespCode fallbackCode) {
return BaseCode.BAD_REQUEST;
}
@Override
protected String getMessage(IRespCode respCode, SpringValidatorException e) {
FieldError error = e.getBindingResult().getFieldError();
if (isNotBlank(error.getDefaultMessage())) {
return format("Validation refused [%s %s]", error.getField(), error.getDefaultMessage());
} else {
return "Validation refused";
}
}
}

View File

@ -0,0 +1,91 @@
package cn.axzo.framework.autoconfigure.web.exception.handler.internal;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler;
import cn.axzo.framework.core.InternalException;
import cn.axzo.framework.domain.ServiceException;
import cn.axzo.framework.domain.web.ApiException;
import cn.axzo.framework.domain.web.code.IRespCode;
import cn.axzo.framework.domain.web.code.RespCode;
import org.springframework.http.HttpStatus;
import java.util.Map;
import static jodd.util.StringUtil.isNotBlank;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
* @Description 标准异常
* @Author liyong.tian
* @Date 2020/9/10 10:51
**/
public class StandardExceptionResultHandler extends AbstractExceptionApiResultHandler<Throwable> {
public StandardExceptionResultHandler(RespErrorCodeMappingProperties properties){
super(properties);
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE - 10;
}
@Override
protected IRespCode decode(Throwable error, IRespCode fallbackCode) {
String errorMsg = error.getMessage();
final String code;
final String message;
if (error instanceof ApiException || error instanceof ServiceException) {
if (error instanceof ApiException) {
code = ((ApiException) error).getCode();
message = errorMsg;
} else {
String serviceCode = ((ServiceException) error).getCode();
if (isNotBlank(serviceCode)) {
code = serviceCode;
message = errorMsg;
} else {
code = fallbackCode.getRespCode();
message = fallbackCode.getMessage();
}
}
} else {
code = fallbackCode.getRespCode();
message = fallbackCode.getMessage();
}
return new RespCode(code, message);
}
@Override
protected HttpStatus mappingHttpStatus(String code, Throwable ex) {
if (ex instanceof ApiException && ((ApiException) ex).isBadRequest()) {
return BAD_REQUEST;
}
return super.mappingHttpStatus(code, ex);
}
@Override
protected String getMessage(IRespCode respCode, Map<String, Object> attributes, Throwable e) {
Object trace = attributes.get("trace");
if (trace != null && e instanceof ServiceException) {
return e.getMessage();
}
return super.getMessage(respCode, attributes, e);
}
@Override
protected Throwable getRealCause(Throwable error) {
while (error.getCause() != null) {
if (error instanceof ServiceException && !(error.getCause() instanceof ServiceException)) {
break;
}
if (error instanceof ApiException && !(error.getCause() instanceof ApiException)) {
break;
}
if (error instanceof InternalException && !(error.getCause() instanceof InternalException)) {
break;
}
error = error.getCause();
}
return error;
}
}

View File

@ -0,0 +1,31 @@
package cn.axzo.framework.autoconfigure.web.exception.handler.internal;
import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties;
import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.code.IRespCode;
import javax.validation.ValidationException;
import java.util.Objects;
/**
* @Description 验证异常
* @Author liyong.tian
* @Date 2020/9/9 20:20
**/
public class ValidationExceptionResultHandler extends AbstractExceptionApiResultHandler<ValidationException> {
public ValidationExceptionResultHandler(RespErrorCodeMappingProperties properties) {
super(properties);
}
@Override
protected IRespCode decode(ValidationException ex, IRespCode fallbackCode) {
return BaseCode.BAD_REQUEST;
}
@Override
protected String getMessage(IRespCode respCode, ValidationException ex) {
return Objects.nonNull(ex.getMessage()) ? ex.getMessage() : respCode.getMessage();
}
}

View File

@ -0,0 +1,15 @@
package cn.axzo.framework.autoconfigure.web.exception.resolver;
import org.springframework.http.HttpStatus;
import java.util.Optional;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/10 10:56
**/
public interface HttpStatusResolver {
Optional<HttpStatus> resolve(Throwable e);
}

View File

@ -0,0 +1,40 @@
package cn.axzo.framework.autoconfigure.web.exception.resolver.internal;
import cn.axzo.framework.autoconfigure.web.exception.resolver.HttpStatusResolver;
import cn.axzo.framework.core.util.ClassUtil;
import io.undertow.server.RequestTooBigException;
import io.undertow.server.handlers.form.MultiPartParserDefinition;
import org.apache.tomcat.util.http.fileupload.FileUploadBase;
import org.apache.tomcat.util.http.fileupload.impl.SizeException;
import org.springframework.http.HttpStatus;
import java.util.Optional;
import static cn.axzo.framework.core.util.ClassUtil.getDefaultClassLoader;
import static org.springframework.http.HttpStatus.PAYLOAD_TOO_LARGE;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/10 10:57
**/
public class FileTooLargeExceptionHttpStatusResolver implements HttpStatusResolver {
private boolean isTomcat = ClassUtil.isPresent("org.apache.catalina.startup.Tomcat", getDefaultClassLoader());
private static boolean isUndertow = ClassUtil.isPresent("io.undertow.Undertow", getDefaultClassLoader());
@Override
public Optional<HttpStatus> resolve(Throwable e) {
if (isUndertow && e instanceof RequestTooBigException) {
return Optional.of(PAYLOAD_TOO_LARGE);
}
if (isUndertow && e instanceof MultiPartParserDefinition.FileTooLargeException) {
return Optional.of(PAYLOAD_TOO_LARGE);
}
if (isTomcat && e instanceof SizeException) {
return Optional.of(PAYLOAD_TOO_LARGE);
}
return Optional.empty();
}
}

View File

@ -0,0 +1,33 @@
package cn.axzo.framework.autoconfigure.web.exception.resolver.internal;
import cn.axzo.framework.autoconfigure.web.exception.resolver.HttpStatusResolver;
import cn.axzo.framework.core.util.ClassUtil;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.firewall.RequestRejectedException;
import java.util.Optional;
import static cn.axzo.framework.core.util.ClassUtil.getDefaultClassLoader;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/10 10:59
**/
public class RequestRejectedExceptionHttpStatusResolver implements HttpStatusResolver {
private static boolean isClassPresent;
static {
String className = "org.springframework.security.web.firewall.RequestRejectedException";
isClassPresent = ClassUtil.isPresent(className, getDefaultClassLoader());
}
@Override
public Optional<HttpStatus> resolve(Throwable e) {
if (isClassPresent && e instanceof RequestRejectedException) {
return Optional.of(HttpStatus.BAD_REQUEST);
}
return Optional.empty();
}
}

View File

@ -0,0 +1,146 @@
package cn.axzo.framework.autoconfigure.web.exception.support;
import cn.axzo.framework.autoconfigure.web.exception.resolver.HttpStatusResolver;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.result.ApiResult;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/10 11:27
**/
@RestController
@RequestMapping(value = "${server.error.path:${error.path:/error}}", produces = APPLICATION_JSON_UTF8_VALUE)
public class GlobalErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
private final ErrorAttributes errorAttributes;
private final List<HttpStatusResolver> httpStatusResolvers;
public GlobalErrorController(ErrorAttributes errorAttributes, ServerProperties properties,
List<HttpStatusResolver> httpStatusResolvers) {
super(errorAttributes);
this.errorProperties = properties.getError();
this.errorAttributes = errorAttributes;
this.httpStatusResolvers = httpStatusResolvers;
}
@RequestMapping
public ResponseEntity<ApiResult<?>> error(HttpServletRequest request) {
// 1.5.X Throwable error = this.errorAttributes.getError(new ServletRequestAttributes(request));
Throwable error = this.errorAttributes.getError(new ServletWebRequest(request));
Cause cause = getRealCause(error);
// 1.获取HTTP状态码
HttpStatus status = getStatus(cause.getError(), request);
// 2.获取code
BaseCode baseCode = BaseCode.parse(status.value());
String code = baseCode.getRespCode();
// 3.获取message
String msg = getMessage(baseCode, status, cause, request);
// 4.响应
val body = ApiResult.err(code, msg);
return new ResponseEntity<>(body, status);
}
@Override
public String getErrorPath() {
return errorProperties.getPath();
}
private HttpStatus getStatus(Throwable e, HttpServletRequest request) {
return httpStatusResolvers.stream()
.map(resolver -> resolver.resolve(e))
.filter(Optional::isPresent)
.findFirst()
.map(Optional::get)
.orElseGet(() -> {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.OK;
}
try {
return HttpStatus.valueOf(statusCode);
} catch (Exception ex) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
});
}
private String getMessage(BaseCode baseCode, HttpStatus status, Cause cause, HttpServletRequest request) {
if (cause.getError() instanceof IllegalArgumentException) {
request.setAttribute(RequestDispatcher.ERROR_MESSAGE, "");
}
Map<String, Object> data = getErrorAttributes(request, isIncludeStackTrace(request));
Object message = data.get("message");
Object trace = data.get("trace");
if (trace != null && status.is5xxServerError()) {
return trace.toString();
} else if (!Objects.equals(message, "No errors") && !Objects.equals(message, "No message available")) {
if (cause.getDepth() > 0) {
return cause.getError().getMessage();
}
return message.toString();
} else {
return baseCode.getMessage();
}
}
/**
* Determine if the stacktrace attribute should be included.
*
* @param request the source request
* @return if the stacktrace attribute should be included
*/
private boolean isIncludeStackTrace(HttpServletRequest request) {
IncludeStacktrace include = errorProperties.getIncludeStacktrace();
return include == IncludeStacktrace.ALWAYS
|| include == IncludeStacktrace.ON_TRACE_PARAM
&& getTraceParameter(request);
}
private Cause getRealCause(Throwable error) {
if (error == null) {
return new Cause(0, null);
}
int depth = 0;
while (depth++ < 3 && error.getCause() != null) {
error = error.getCause();
}
return new Cause(depth - 1, error);
}
@RequiredArgsConstructor
@Getter
private class Cause {
private final int depth;
private final Throwable error;
}
}

View File

@ -0,0 +1,288 @@
package cn.axzo.framework.autoconfigure.web.exception.support;
import cn.axzo.framework.autoconfigure.web.exception.handler.ExceptionResultHandler;
import cn.axzo.framework.autoconfigure.web.exception.handler.internal.StandardExceptionResultHandler;
import cn.axzo.framework.core.InternalException;
import cn.axzo.framework.core.util.ClassUtil;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.framework.domain.web.result.Result;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static java.lang.String.format;
import static jodd.util.StringUtil.isNotBlank;
/**
* @Description spring mvc全局异常捕获
* @Author liyong.tian
* @Date 2020/9/10 11:41
**/
@RestControllerAdvice(annotations = {Controller.class, RestController.class})
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private final List<ExceptionResultHandler<? extends Throwable, ? extends Result>> handlers;
private final ErrorProperties errorProperties;
private final ErrorAttributes errorAttributes;
public GlobalExceptionHandler(List<ExceptionResultHandler<? extends Throwable, ? extends Result>> handlers,
ErrorAttributes errorAttributes,
ServerProperties properties) {
this.handlers = handlers;
this.errorAttributes = errorAttributes;
this.errorProperties = properties.getError();
}
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers,
HttpStatus status, WebRequest request) {
if (body instanceof Result) {
return super.handleExceptionInternal(ex, body, headers, status, request);
}
Result result = _handleException(request, ex).orElseGet(() -> {
// 1.获取code
String code = BaseCode.parse(status.value()).getRespCode();
// 2.获取message
String message;
Map<String, Object> data = Requests.from(request).getErrorAttributes(errorAttributes, errorProperties);
Object trace = data.get("trace");
if (trace != null && status.is5xxServerError()) {
message = trace.toString();
} else {
message = ex.getMessage();
}
// 3.响应
return ApiResult.err(code, message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
/**
* Get和表单请求参数错误
*/
@Override
protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status,
WebRequest request) {
Result result = _handleException(request, ex).orElseGet(() -> {
// 1.获取code
String code = BaseCode.parse(status.value()).getRespCode();
// 2.获取message
String message;
FieldError error = ex.getFieldError();
if (isNotBlank(error.getDefaultMessage())) {
message = format("Invalid parameter [%s %s]", error.getField(), error.getDefaultMessage());
} else {
message = "Invalid parameter";
}
// 3.响应
return ApiResult.err(code, message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
/**
* Get请求参数类型不匹配
*/
@Override
protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
Result result = _handleException(request, ex).orElseGet(() -> {
// 1.获取code
String code = BaseCode.parse(status.value()).getRespCode();
// 2.获取message
String message;
if (ex instanceof MethodArgumentTypeMismatchException) {
String field = ((MethodArgumentTypeMismatchException) ex).getName();
message = format("Type mismatched [%s = %s]", field, ex.getValue());
} else {
message = format("Type mismatched for value [%s]", ex.getValue());
}
// 3.响应
return ApiResult.err(code, message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
/**
* 请求字段校验不通过Validation规范
*/
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status,
WebRequest request) {
Result result = _handleException(request, ex).orElseGet(() -> {
// 1.获取code
String code = BaseCode.parse(status.value()).getRespCode();
// 2.获取message
String message;
FieldError error = ex.getBindingResult().getFieldError();
if (isNotBlank(error.getDefaultMessage())) {
message = format("%s %s", error.getField(), error.getDefaultMessage());
} else {
message = "Validation refused";
}
// 3.响应
return ApiResult.err(code, message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
/**
* Get请求参数不存在
*/
@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpHeaders headers, HttpStatus status,
WebRequest request) {
Result result = _handleException(request, ex).orElseGet(() -> {
// 1.获取code
String code = BaseCode.parse(status.value()).getRespCode();
// 2.获取message
String message;
String field = ex.getParameterName();
message = "Required parameter '" + field + "' is not present";
// 3.响应
return ApiResult.err(code, message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
/**
* 请求体格式错误
*/
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status,
WebRequest request) {
Result result = _handleException(request, ex).orElseGet(() -> {
// 1.获取code
String code = BaseCode.parse(status.value()).getRespCode();
// 2.获取message
String message;
Throwable e = ex.getCause();
String fallbackMessage = "Request body contains invalid format";
if (e == null) {
message = "Required request body is missing";
} else if (e instanceof InvalidFormatException) {
// Json请求数据格式错误
InvalidFormatException ife = ((InvalidFormatException) e);
message = ife.getPath().stream()
.map(JsonMappingException.Reference::getFieldName)
.reduce((field1, field2) -> field1 + "." + field2)
.map(field -> format("Invalid format [%s = %s]", field, ife.getValue()))
.orElse(fallbackMessage);
} else if (e instanceof JsonMappingException) {
// Json请求数据格式错误
JsonMappingException jme = (JsonMappingException) e;
message = jme.getPath().stream()
.map(JsonMappingException.Reference::getFieldName)
.reduce((field1, field2) -> field1 + "." + field2)
.map(field -> format("Invalid format [%s]", field))
.orElse(fallbackMessage);
} else if (e instanceof JsonParseException) {
message = "Json request body contains invalid format";
} else {
message = fallbackMessage;
}
// 3.响应
return ApiResult.err(code, message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
@ExceptionHandler(MultipartException.class)
public void handleMultipartException(MultipartException e) throws MultipartException {
throw e;
}
@ExceptionHandler(Throwable.class)
public Result handleAllException(HttpServletRequest request, HttpServletResponse response, Throwable e) {
Map<String, Object> attributes = Requests.from(request).getErrorAttributes(errorAttributes, errorProperties);
return _handleException(request, response, e, attributes)
.orElseGet(() -> handlers.stream()
.filter(handler -> handler.getExceptionClass() == Throwable.class)
.findFirst()
.map(handler -> handler.handle(request, response, ClassUtil.cast(e), attributes))
.orElseThrow(() -> new InternalException("No exception handler with: " + e.getClass()))
);
}
private Optional<Result> _handleException(WebRequest webRequest, Throwable e) {
Map<String, Object> attributes = Requests.from(webRequest).getErrorAttributes(errorAttributes, errorProperties);
if (webRequest instanceof ServletWebRequest) {
HttpServletRequest request = ((ServletWebRequest) webRequest).getRequest();
HttpServletResponse response = ((ServletWebRequest) webRequest).getResponse();
return _handleException(request, response, e, attributes);
}
return Optional.empty();
}
private Optional<Result> _handleException(HttpServletRequest request, HttpServletResponse response, Throwable e,
Map<String, Object> attributes) {
Optional<Result> resultOptional = handlers.stream()
.filter(handler -> shouldFilter(handler, e))
.<Result>map(handler -> handler.handle(request, response, ClassUtil.cast(e), attributes))
.findFirst();
if (resultOptional.isPresent()) {
return resultOptional;
} else {
return handlers.stream()
.filter(handler -> handler.getClass() != StandardExceptionResultHandler.class)
.filter(handler -> handler.getExceptionClass() == Throwable.class)
.findFirst()
.map(handler -> handler.handle(request, response, ClassUtil.cast(e), attributes));
}
}
private boolean shouldFilter(ExceptionResultHandler<? extends Throwable, ? extends Result> handler, Throwable e) {
if (handler.isRecursive()) {
return handler.getExceptionClass().isAssignableFrom(e.getClass());
}
return handler.getExceptionClass() == e.getClass();
}
}

View File

@ -0,0 +1,64 @@
package cn.axzo.framework.autoconfigure.web.exception.support;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/10 11:08
**/
public class Requests {
public static RequestHelper from(RequestAttributes attributes) {
return new RequestHelper(attributes);
}
public static RequestHelper from(HttpServletRequest request) {
return new RequestHelper(request);
}
public static class RequestHelper {
private final RequestAttributes attributes;
private final HttpServletRequest request;
private RequestHelper(RequestAttributes attributes) {
this.attributes = attributes;
this.request = (HttpServletRequest) attributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
}
private RequestHelper(HttpServletRequest request) {
this.attributes = new ServletWebRequest(request);
this.request = request;
}
public Map<String, Object> 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());
}
}
}

View File

@ -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";
}
}

View File

@ -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.
* <p>
* 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<SwaggerCustomizer> swaggerCustomizers,
ObjectProvider<AlternateTypeRule[]> 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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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
}
]
}

View File

@ -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

85
axzo-common-boot/pom.xml Normal file
View File

@ -0,0 +1,85 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>axzo-framework-commons</artifactId>
<groupId>cn.axzo.framework</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>axzo-common-boot</artifactId>
<name>Axzo Common Boot</name>
<properties>
<pkgVersion.dir>cn/axzo/framework/boot</pkgVersion.dir>
</properties>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>cn.axzo.framework.framework</groupId>
<artifactId>axzo-common-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<!-- Optional -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<executions>
<execution>
<id>process-packageVersion</id>
<goals>
<goal>replace</goal>
</goals>
<phase>generate-sources</phase>
</execution>
</executions>
<configuration>
<file>${basedir}/src/main/java/${pkgVersion.dir}/PackageVersion.java.in</file>
<outputFile>${project.basedir}/src/main/java/${pkgVersion.dir}/PackageVersion.java</outputFile>
<replacements>
<replacement>
<token>@package@</token>
<value>${project.groupId}.boot</value>
</replacement>
<replacement>
<token>@projectversion@</token>
<value>${project.version}</value>
</replacement>
<replacement>
<token>@spring_cloud_version@</token>
<value>${spring-cloud.version}</value>
</replacement>
</replacements>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -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<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
DefaultProfileUtil.setDefaultProfile(event.getSpringApplication());
}
}

View File

@ -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 <code>spring.profiles.active</code> set in the environment or as command line argument.
* If the value is not available in <code>application.yml</code> then <code>local</code> 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<String, Object> defProperties = new HashMap<>();
/*
* The default profile to use when no other profiles are defined
* This cannot be set in the <code>application.yml</code> 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;
}
}

View File

@ -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<String, Object> 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));
}
}

View File

@ -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<String> fetchLocalIp(Environment env) {
int timeoutSeconds = fetchLocalIpTimeoutSeconds(env);
try {
return Optional.ofNullable(Inets.fetchLocalIp(timeoutSeconds));
} catch (FetchException e) {
return Optional.empty();
}
}
}

View File

@ -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";
}

View File

@ -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@";
}

View File

@ -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<String, Object> 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<String, Object> getSubProperties(PropertySources propertySources, String rootPrefix,
String keyPrefix) {
RelaxedNames keyPrefixes = new RelaxedNames(keyPrefix);
Map<String, Object> subProperties = new LinkedHashMap<String, Object>();
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;
}
}

View File

@ -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<String> {
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<String> values = new LinkedHashSet<String>();
/**
* 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<String> iterator() {
return this.values.iterator();
}
private void initialize(String name, Set<String> 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());
}
}

View File

@ -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<ApplicationPreparedEvent> {
@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<String> 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.";
}
}

View File

@ -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<PropertySource<?>> load(String name, Resource resource) throws IOException {
Properties properties = new Properties();
properties.load(new InputStreamReader(resource.getInputStream(), UTF_8));
if (!properties.isEmpty()) {
List<PropertySource<?>> list = Lists.newArrayList();
list.add(new PropertiesPropertySource(name, properties));
return list;
}
return null;
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + LOWEST_PRECEDENCE;
}
}

View File

@ -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<String, Object> 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;
}
}

View File

@ -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;
}
}

View File

@ -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<String, Object> properties;
private static final Map<String, Object> propertiesWithoutBootstrap;
static {
Map<String, Object> 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;
}
}

View File

@ -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<String> 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);
}
}

View File

@ -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<String, Object> properties = resolveEnvironment(environment);
Map<String, Object> systemProperties = newHashMap(environment.getSystemProperties());
Map<String, Object> 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<String, Object> resolveEnvironment(ConfigurableEnvironment environment) {
//spring-boot-2.0.X版本自定义getSubProperties方法 spring-boot-1.5.X版本引用PropertySourceUtils.getSubProperties()方法
Map<String, Object> properties = getSubProperties(environment.getPropertySources(), null);
Map<String, Object> 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<String, Object> properties = getSubProperties(environment.getPropertySources(), null);
Map<String, Object> 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<String, Object> getSubProperties(PropertySources propertySources) {
Iterator<PropertySource<?>> sourceIterator = propertySources.iterator();
Map<String, Object> 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));
}
}
}
}*/
}

View File

@ -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<ApplicationEnvironmentPreparedEvent>, 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;
}
}

View File

@ -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<ApplicationStartingEvent>, Ordered {
@Override
public void onApplicationEvent(@Nonnull ApplicationStartingEvent event) {
val handler = new Log4j2MDCHandler(event.getSpringApplication().getClassLoader());
handler.setup();
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="600">
<Properties>
<Property name="LOG_HOME">${ctx:logging.path}</Property>
<Property name="APP_NAME">${ctx:spring.application.name}</Property>
<Property name="LOG_LEVEL">${ctx:logging.level.root}</Property>
<Property name="PID">????</Property>
<Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
<Property name="LOG_LEVEL_PATTERN">%5p</Property>
<Property name="LOG_CONSOLE_PATTERN">%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}</Property>
<Property name="LOG_FILE_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN} ${sys:PID} --- [%t] %c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
</Properties>
<!--日志输出器-->
<Appenders>
<Console name="Console" direct="true">
<PatternLayout pattern="${LOG_CONSOLE_PATTERN}" />
</Console>
<RollingRandomAccessFile name="File" fileName="${LOG_HOME}/${APP_NAME}.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/${APP_NAME}-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout pattern="${LOG_FILE_PATTERN}" />
<Policies>
<SizeBasedTriggeringPolicy size="50 MB" />
</Policies>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="Error-File" fileName="${LOG_HOME}/${APP_NAME}-error.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/${APP_NAME}-%d{yyyy-MM-dd-HH}-%i.log.gz">
<ThresholdFilter level="error"/>
<PatternLayout pattern="${LOG_FILE_PATTERN}" />
<Policies>
<SizeBasedTriggeringPolicy size="50 MB" />
</Policies>
</RollingRandomAccessFile>
</Appenders>
<!--日志记录器-->
<Loggers>
<AsyncLogger name="org.springframework" level="info" />
<AsyncLogger name="springfox.documentation" level="info" />
<AsyncLogger name="org.apache.http" level="info" />
<AsyncRoot level="${LOG_LEVEL}">
<AppenderRef ref="Console" />
<AppenderRef ref="File" />
<AppenderRef ref="Error-File" />
</AsyncRoot>
</Loggers>
</Configuration>

View File

@ -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;
}
}

View File

@ -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<ApplicationReadyEvent> {
@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);
}
});
}
}

View File

@ -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<ApplicationEnvironmentPreparedEvent>, 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;
}
}

View File

@ -0,0 +1,21 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>axzo-framework-commons</artifactId>
<groupId>cn.axzo.framework</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<groupId>cn.axzo.framework.client</groupId>
<artifactId>axzo-common-clients</artifactId>
<packaging>pom</packaging>
<name>Axzo Common Client Parent</name>
<modules>
<module>retrofit-starter</module>
</modules>
</project>

View File

@ -0,0 +1,38 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.axzo.framework.client</groupId>
<artifactId>axzo-common-clients</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>retrofit-starter</artifactId>
<name>Axzo Common Client Retrofit Starter</name>
<dependencies>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-common-domain</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework.jackson</groupId>
<artifactId>jackson-starter</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>adapter-rxjava2</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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> T target(String baseUrl, Class<T> 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);
}
}

View File

@ -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<String> 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<String> 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();
}
}

View File

@ -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");
}

View File

@ -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<String> 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<String> getBody(Response response) {
if (response == null || !HttpHeaders.hasBody(response) || _bodyEncoded(response.headers())) {
return empty();
}
return getBody(response.body());
}
/**
* 获取响应体内容
*
* @param responseBody 响应体对象
*/
public static Optional<String> 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");
}
}

View File

@ -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> T execute(Call<T> call, Function<IOException, T> fallbackHandler) {
try {
return execute(call);
} catch (IOException e) {
return fallbackHandler.apply(e);
}
}
@Nonnull
public <T> T execute(Call<T> call) throws IOException {
try {
Response<T> 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);
}
}
}

View File

@ -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<T> implements Converter<T, RequestBody> {
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);
}
}

View File

@ -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<T> implements Converter<ResponseBody, T> {
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();
}
}
}

View File

@ -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<String, RequestBody> {
private final MediaType contentType;
@Override
public RequestBody convert(String s) {
byte[] bytes = s.getBytes(Charset.defaultCharset());
return RequestBody.create(contentType, bytes);
}
}

View File

@ -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<ResponseBody, String> {
@Override
public String convert(ResponseBody value) throws IOException {
return value.string();
}
}

64
axzo-common-core/pom.xml Normal file
View File

@ -0,0 +1,64 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>axzo-framework-commons</artifactId>
<groupId>cn.axzo.framework</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>axzo-common-core</artifactId>
<name>Axzo Common Core</name>
<dependencies>
<!--Compile-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<!--高性能的 Java 缓存库-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jool</artifactId>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>joor</artifactId>
</dependency>
<dependency>
<groupId>org.jodd</groupId>
<artifactId>jodd-bean</artifactId>
</dependency>
<dependency>
<groupId>org.javatuples</groupId>
<artifactId>javatuples</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<!--Test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -0,0 +1,9 @@
package cn.axzo.framework.core;
/**
* @author liyong.tian
* @since 2020/8/12 16:17
*/
public interface IName {
String getName();
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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]+$";
}

View File

@ -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";
}

View File

@ -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 <T> void process(Collection<T> collection, Consumer<T> handler) {
process(collection, handler, t -> {
});
}
public <T> void process(Collection<T> collection, Consumer<T> handler, Consumer<T> finallyHandler) {
process(collection, handler, (t, e) -> log.error(Constants.JOB_MARKER, format("%s error, data = %s", batchName, t)), finallyHandler);
}
public <T> void process(Collection<T> collection, Consumer<T> handler, BiConsumer<T, Throwable> onError) {
process(collection, handler, onError, t -> {
});
}
/**
* 执行批处理(同步方法)
* <p>
* : 该方法会阻塞当前线程, 直到批处理完成
*
* @param collection 待处理记录
* @param handler 单条处理方法
* @param onError 自定义单条处理异常后的行为
*/
public <T> void process(Collection<T> collection, Consumer<T> handler, BiConsumer<T, Throwable> onError,
Consumer<T> 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<Callable<Object>> callableList = collection.stream().map(t -> (Callable<Object>) () -> {
if (error.get() > errorLimitInclude) {
return null;
}
try {
handler.accept(t);
success.getAndIncrement();
} catch (Throwable e) {
log.error(Constants.JOB_MARKER, format("%s error, data = %s", batchName, t), e);
error.getAndIncrement();
onError.accept(t, e);
} finally {
finallyHandler.accept(t);
}
return null;
}).collect(toList());
try {
pool.invokeAll(callableList);
} catch (InterruptedException ignore) {
log.error(Constants.JOB_MARKER, format("%s error", batchName), ignore);
}
long executionMills = currentTimeMillis() - startTime;
log.info(Constants.JOB_MARKER, format("%s successSize = %d", batchName, success.get()));
log.info(Constants.JOB_MARKER, format("%s errorSize = %d", batchName, error.get()));
log.info(Constants.JOB_MARKER, format("%s tookMs = %d", batchName, executionMills));
if (autoShutdown) {
shutdownAndAwaitTermination();
}
}
private void shutdownAndAwaitTermination() {
pool.shutdown();
try {
if (!pool.awaitTermination(timeout, unit)) {
pool.shutdownNow();
if (!pool.awaitTermination(timeout, unit)) {
log.error(Constants.JOB_MARKER, "pool did not terminate");
}
}
} catch (InterruptedException e) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
private void validatePercentage(Integer errorThresholdPercentage) {
if (errorThresholdPercentage < 0 || errorThresholdPercentage > 100) {
throw new IllegalArgumentException("错误百分比阈值超出取值范围[0-100]");
}
}
}

View File

@ -0,0 +1,23 @@
package cn.axzo.framework.core.concurrent;
import cn.axzo.framework.core.InternalException;
import java.util.concurrent.TimeUnit;
/**
* @author liyong.tian
* @since 2020/8/12 16:23
*/
public final class Idles {
public static void idle(final long duration, final TimeUnit unit) {
try {
unit.sleep(duration);
} catch (InterruptedException e) {
throw new InternalException(e);
}
}
private Idles() {
}
}

View File

@ -0,0 +1,232 @@
package cn.axzo.framework.core.crypto;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;
import java.util.UUID;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* AES工具类
* size指字符串的位数不是字节数
*
* @author liyong.tian
* @since 2020/8/12 16:49
*/
@Slf4j
@SuppressWarnings({"WeakerAccess", "unused"})
public class AES {
/**
* 伪随机数生成算法
*/
private static final String PRNG_ALGORITHM = "SHA1PRNG";
private static final String ALGORITHM = "AES";
private static final int DEFAULT_KEY_SIZE = 128;
/**
* AES密钥
*/
private final Key key;
private final boolean isUrlSafe;
private final String padding;
@Nullable
private final AlgorithmParameterSpec iv;
private AES(Key key, boolean isUrlSafe, String padding, @Nullable AlgorithmParameterSpec iv) {
this.key = key;
this.isUrlSafe = isUrlSafe;
this.padding = padding;
this.iv = iv;
}
public static AESBuilder builder() {
return new AESBuilder();
}
/**
* 加密
*
* @param text 明文
* @return 密文Base64
*/
public String encrypt(String text) {
try {
return Ciphers.encrypt(key, iv, text, padding, isUrlSafe);
} catch (Exception e) {
log.error("AES加密失败", e);
throw new AESException("AES加密失败", e);
}
}
/**
* 解密
*
* @param encryptedText 密文Base64
* @return 明文
*/
public String decrypt(String encryptedText) {
try {
return Ciphers.decrypt(key, iv, encryptedText, padding, isUrlSafe);
} catch (Exception e) {
log.error("AES解密失败", e);
throw new AESException("AES解密失败", e);
}
}
/**
* 指定长度的密钥
*
* @param key 密钥哈希
* @param isBase64Encoded 该密钥是否经过Base64加密
* @param size 密钥位数
* @return 密钥
*/
private static Key toKey(String key, boolean isBase64Encoded, int size) {
try {
//必须是8的倍数
if (size % 8 != 0) {
throw new AESException("密钥位数必须是8的倍数");
}
byte[] keyBytes;
if (isBase64Encoded) {
keyBytes = Base64.getDecoder().decode(key);
} else {
keyBytes = padWithZeros(key.getBytes(UTF_8), size / 8);
}
return new SecretKeySpec(keyBytes, ALGORITHM);
} catch (Exception e) {
log.error("生成AES密钥失败", e);
throw new AESException("生成AES密钥失败", e);
}
}
/**
* 随机生成AES密钥
*
* @param seed 随机种子
* @param size 密钥位数
* @return AES密钥
*/
private static Key generateKey(String seed, int size) {
try {
//随机数
SecureRandom secureRandom = SecureRandom.getInstance(PRNG_ALGORITHM);
secureRandom.setSeed(seed.getBytes(Charset.defaultCharset()));
//初始化密钥
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
keyGenerator.init(size, secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
byte[] bytes = secretKey.getEncoded();
return new SecretKeySpec(bytes, ALGORITHM);
} catch (Exception e) {
log.error("生成AES密钥失败", e);
throw new AESException("生成AES密钥失败", e);
}
}
/**
* 生成AES算法向量
*
* @param iv 向量哈希
* @return AES算法向量
*/
private static AlgorithmParameterSpec ivSpec(String iv, int size) {
try {
//必须是8的倍数
if (size % 8 != 0) {
throw new AESException("算法向量位数必须是8的倍数");
}
byte[] ivBytes = padWithZeros(iv.getBytes(UTF_8), size / 8);
return new IvParameterSpec(ivBytes);
} catch (Exception e) {
log.error("生成AES算法向量失败", e);
throw new AESException("生成AES算法向量失败", e);
}
}
/**
* 补位0到指定长度
*/
private static byte[] padWithZeros(byte[] input, int lenNeed) {
int rest = input.length % lenNeed;
if (rest > 0) {
byte[] result = new byte[input.length + (lenNeed - rest)];
System.arraycopy(input, 0, result, 0, input.length);
return result;
}
return input;
}
public static class AESBuilder {
private String padding = "AES";
private Key key;
private boolean isUrlSafe;
private AlgorithmParameterSpec iv;
AESBuilder() {
}
public AESBuilder genKey(int size) {
return genKey(UUID.randomUUID().toString(), size);
}
public AESBuilder genKey(String seed, int size) {
this.key = generateKey(seed, size);
return this;
}
public AESBuilder key(String key, int size) {
this.key = toKey(key, false, size);
return this;
}
public AESBuilder key(String key, boolean isBase64Encoded, int size) {
this.key = toKey(key, isBase64Encoded, size);
return this;
}
public AESBuilder isUrlSafe(boolean isUrlSafe) {
this.isUrlSafe = isUrlSafe;
return this;
}
public AESBuilder padding(String padding) {
this.padding = padding;
return this;
}
public AESBuilder iv(String iv, int size) {
this.iv = ivSpec(iv, size);
return this;
}
public AES build() {
if (key == null) {
this.key = generateKey(UUID.randomUUID().toString(), DEFAULT_KEY_SIZE);
}
return new AES(key, isUrlSafe, padding, iv);
}
}
}

View File

@ -0,0 +1,29 @@
package cn.axzo.framework.core.crypto;
/**
* AES加密异常
* @author liyong.tian
* @since 2020/8/12 16:51
*/
public class AESException extends RuntimeException {
public AESException() {
super();
}
public AESException(String message) {
super(message);
}
public AESException(String message, Throwable cause) {
super(message, cause);
}
public AESException(Throwable cause) {
super(cause);
}
protected AESException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,63 @@
package cn.axzo.framework.core.crypto;
import lombok.experimental.UtilityClass;
import javax.crypto.Cipher;
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* @author liyong.tian
* @since 2020/8/12 16:52
*/
@UtilityClass
public class Ciphers {
/**
* 加密
*
* @param text 明文
* @return 密文Base64
*/
public String encrypt(Key key, AlgorithmParameterSpec iv, String text, String padding, boolean isUrlSafe) throws Exception {
Cipher cipher = Cipher.getInstance(padding);
if (iv == null) {
cipher.init(Cipher.ENCRYPT_MODE, key);
} else {
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
}
byte[] bytes = cipher.doFinal(text.getBytes(UTF_8));
Base64.Encoder encoder = isUrlSafe ? Base64.getUrlEncoder() : Base64.getEncoder();
return new String(encoder.encode(bytes), UTF_8);
}
/**
* 解密
*
* @param encryptedText 密文Base64
* @return 明文
*/
public String decrypt(Key key, AlgorithmParameterSpec iv, String encryptedText, String padding, boolean isUrlSafe) throws Exception {
Cipher cipher = Cipher.getInstance(padding);
if (iv == null) {
cipher.init(Cipher.DECRYPT_MODE, key);
} else {
cipher.init(Cipher.DECRYPT_MODE, key, iv);
}
cipher.init(Cipher.DECRYPT_MODE, key, iv);
Base64.Decoder decoder = isUrlSafe ? Base64.getUrlDecoder() : Base64.getDecoder();
byte[] bytes = cipher.doFinal(decoder.decode(encryptedText));
return new String(bytes, UTF_8);
}
public String encrypt(Key key, String text, String padding, boolean isUrlSafe) throws Exception {
return encrypt(key, null, text, padding, isUrlSafe);
}
public String decrypt(Key key, String encryptedText, String padding, boolean isUrlSafe) throws Exception {
return decrypt(key, null, encryptedText, padding, isUrlSafe);
}
}

View File

@ -0,0 +1,85 @@
package cn.axzo.framework.core.crypto;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import java.security.Key;
import java.util.Base64;
/**
* @author liyong.tian
* @since 2020/8/12 16:56
*/
@Slf4j
public class DESede {
/**
* 密钥算法
*/
private static final String ALGORITHM = "DESede";
/**
* 密钥哈希Base64
*/
@Getter
private final String key;
/**
* 密钥算法/工作模式/填充方式
*/
private final String padding;
private final boolean isUrlSafe;
public DESede(String desedeKey, String padding) {
this(desedeKey, padding, false);
}
public DESede(String desedeKey, String padding, boolean isUrlSafe) {
this.key = desedeKey;
this.padding = padding;
this.isUrlSafe = isUrlSafe;
}
/**
* 加密
*
* @param text 明文
* @return 密文Base64
*/
public String encrypt(String text) {
try {
return Ciphers.encrypt(toKey(), text, padding, isUrlSafe);
} catch (Exception e) {
log.error("加密失败", e);
throw new DESedeException("加密失败", e);
}
}
/**
* 解密
*
* @param encryptedText 密文Base64
* @return 明文
*/
public String decrypt(String encryptedText) {
try {
return Ciphers.decrypt(toKey(), encryptedText, padding, isUrlSafe);
} catch (Exception e) {
log.error("加密失败", e);
throw new DESedeException("加密失败", e);
}
}
private Key toKey() throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(key);
//实例化Des密钥
DESedeKeySpec keySpec = new DESedeKeySpec(keyBytes);
//实例化密钥工厂
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
//生成密钥
return keyFactory.generateSecret(keySpec);
}
}

View File

@ -0,0 +1,30 @@
package cn.axzo.framework.core.crypto;
/**
* DESeds加密异常
*
* @author liyong.tian
* @since 2020/8/12 16:58
*/
public class DESedeException extends RuntimeException {
public DESedeException() {
super();
}
public DESedeException(String message) {
super(message);
}
public DESedeException(String message, Throwable cause) {
super(message, cause);
}
public DESedeException(Throwable cause) {
super(cause);
}
protected DESedeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,153 @@
package cn.axzo.framework.core.crypto;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import static jodd.util.StringPool.UTF_8;
/**
* RSA工具类
* <p>
* 在spring环境中建议注册为bean实例
*
* @author liyong.tian
* @since 2020/8/12 16:53
*/
@Slf4j
public class RSA {
private static final String ALGORITHM = "RSA";
private static final String SIGN_ALGORITHM = "SHA1WithRSA";
private final PublicKey publicKey;
private final PrivateKey privateKey;
/**
* @param publicKey 公钥Base64
* @param privateKey 私钥Base64
*/
public RSA(String publicKey, String privateKey) {
this.publicKey = loadPublicKey(publicKey);
this.privateKey = loadPrivateKey(privateKey);
}
/**
* 公钥加密
*
* @param text 明文
* @return 密文Base64
*/
public String encrypt(String text) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(text.getBytes(UTF_8));
return new String(Base64.getEncoder().encode(bytes), UTF_8);
} catch (Exception e) {
log.error("加密失败", e);
throw new RSAException("加密失败", e);
}
}
/**
* 私钥解密
*
* @param encryptedText 密文Base64
* @return 明文
*/
public String decrypt(String encryptedText) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
return new String(bytes, UTF_8);
} catch (Exception e) {
log.error("解密失败", e);
throw new RSAException("解密失败", e);
}
}
/**
* 私钥签名
*
* @param text 文本
* @return 签名Base64
*/
public String sign(String text) {
try {
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initSign(privateKey);
signature.update(text.getBytes(UTF_8));
byte[] bytes = signature.sign();
return new String(Base64.getEncoder().encode(bytes), UTF_8);
} catch (Exception e) {
log.error("签名失败", e);
throw new RSAException("签名失败", e);
}
}
/**
* 公钥验签
*
* @param sign 签名参数
* @param text 文本
* @return 验签结果
*/
public boolean verify(String sign, String text) {
try {
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initVerify(publicKey);
signature.update(text.getBytes(UTF_8));
return signature.verify(Base64.getDecoder().decode(sign.getBytes(UTF_8)));
} catch (Exception e) {
log.error("验签失败", e);
return false;
}
}
/**
* 加载公钥
*
* @param publicKey 公钥Base64
* @return 公钥对象
*/
private static PublicKey loadPublicKey(String publicKey) {
try {
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKey);
KeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePublic(keySpec);
} catch (Exception e) {
log.error("公钥加载失败", e);
throw new RSAException("公钥加载失败", e);
}
}
/**
* 加载私钥
*
* @param privateKey 私钥Base64
* @return 私钥对象
*/
private static PrivateKey loadPrivateKey(String privateKey) {
try {
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKey);
KeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePrivate(keySpec);
} catch (Exception e) {
log.error("私钥加载失败", e);
throw new RSAException("私钥加载失败", e);
}
}
}

View File

@ -0,0 +1,30 @@
package cn.axzo.framework.core.crypto;
/**
* RSA加密异常
*
* @author liyong.tian
* @since 2020/8/12 16:55
*/
public class RSAException extends RuntimeException {
public RSAException() {
super();
}
public RSAException(String message) {
super(message);
}
public RSAException(String message, Throwable cause) {
super(message, cause);
}
public RSAException(Throwable cause) {
super(cause);
}
protected RSAException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,69 @@
package cn.axzo.framework.core.currency;
import lombok.Getter;
import java.beans.ConstructorProperties;
import java.util.Objects;
/**
* 该模型的序列化规则存Long 取Cent 展示String
*
* @author liyong.tian
* @since 16/9/19
*/
@Getter
public class Cent {
private final Long value;
private final String yuan;
@ConstructorProperties({"value", "yuan"})
public Cent(Long value, String yuan) {
this(value);
if (!Objects.equals(yuan, this.yuan)) {
throw new IllegalArgumentException("元和分不匹配");
}
}
public Cent(Long value) {
this.value = value;
this.yuan = toYuan(value);
}
public Cent(String yuan) {
this.value = toValue(yuan);
this.yuan = toYuan(value);
}
public String yuan() {
return this.yuan;
}
public Long value() {
return this.value;
}
private String toYuan(Long value) {
return PriceUtil.toYuanString(value);
}
private Long toValue(String yuan) {
return PriceUtil.toCentValue(yuan);
}
@Override
public String toString() {
return Objects.toString(value);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Long && value == ((Long) obj).longValue();
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}

View File

@ -0,0 +1,32 @@
package cn.axzo.framework.core.currency;
import java.math.BigDecimal;
import static java.math.BigDecimal.ROUND_HALF_UP;
import static java.math.BigInteger.valueOf;
/**
* 定点数相关转换
*
* @author liyong.tian
* @since 2017/8/16 上午12:06
*/
public abstract class Decimals {
// 一位精度的10
public static final BigDecimal TEN_ONE_SCALE = new BigDecimal(valueOf(100L), 1);
// 两位精度的100
public static final BigDecimal HUNDRED_TWO_SCALE = new BigDecimal(valueOf(10000L), 2);
// 三位精度的1000
public static final BigDecimal THOUSAND_THREE_SCALE = new BigDecimal(valueOf(1000000L), 3);
public static BigDecimal fractionToDecimal(Long numerator, BigDecimal denominator) {
return new BigDecimal(numerator).divide(denominator, denominator.scale(), ROUND_HALF_UP);
}
public static Long multiplicationToLong(BigDecimal baseValue, BigDecimal multiple) {
return baseValue.multiply(multiple).longValueExact();
}
}

Some files were not shown because too many files have changed in this diff Show More