基础类库和框架实践初始化
This commit is contained in:
parent
7a7a77b0fa
commit
bb090ffd40
39
.gitignore
vendored
Normal file
39
.gitignore
vendored
Normal 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
|
||||
99
axzo-common-autoconfigure/pom.xml
Normal file
99
axzo-common-autoconfigure/pom.xml
Normal 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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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 "";
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Service layer beans.
|
||||
*/
|
||||
package cn.axzo.framework.autoconfigure;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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状态码,非基础错误响应码都映射到451(UNAVAILABLE_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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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
85
axzo-common-boot/pom.xml
Normal 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>
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
@ -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@";
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
48
axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/CheckActiveProfilesListener.java
vendored
Normal file
48
axzo-common-boot/src/main/java/cn/axzo/framework/boot/env/CheckActiveProfilesListener.java
vendored
Normal 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.";
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
21
axzo-common-clients/pom.xml
Normal file
21
axzo-common-clients/pom.xml
Normal 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>
|
||||
38
axzo-common-clients/retrofit-starter/pom.xml
Normal file
38
axzo-common-clients/retrofit-starter/pom.xml
Normal 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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
64
axzo-common-core/pom.xml
Normal 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>
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package cn.axzo.framework.core;
|
||||
|
||||
/**
|
||||
* @author liyong.tian
|
||||
* @since 2020/8/12 16:17
|
||||
*/
|
||||
public interface IName {
|
||||
String getName();
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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]+$";
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
@ -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]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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() {
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
Loading…
Reference in New Issue
Block a user