Commit 7be1a2da authored by 孙 楠's avatar 孙 楠

update dependency version

parent cb626872
package cn.quantgroup.tech.brave.configuration; package cn.quantgroup.tech.brave.configuration;
import cn.quantgroup.tech.brave.interceptor.*; import cn.quantgroup.tech.brave.interceptor.HttpClientRequestInterceptor;
import cn.quantgroup.tech.brave.interceptor.OkHttpClientInterceptor;
import cn.quantgroup.tech.brave.interceptor.RestTemplateRequestInterceptor;
import cn.quantgroup.tech.brave.interceptor.impl.*; import cn.quantgroup.tech.brave.interceptor.impl.*;
import cn.quantgroup.tech.brave.job.TokenJob;
import okhttp3.Interceptor; import okhttp3.Interceptor;
import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpRequestInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
...@@ -88,11 +89,5 @@ public class AuthAutoConfiguration { ...@@ -88,11 +89,5 @@ public class AuthAutoConfiguration {
return new RestTemplateAuthRequestInterceptor(); return new RestTemplateAuthRequestInterceptor();
} }
} }
@Bean
@ConditionalOnProperty(prefix = "tech.auth", name = {"http", "clientId", "secret", "tokenUrl"})
public TokenJob tokenJob() {
return new TokenJob();
}
} }
} }
package cn.quantgroup.tech.brave.configuration; package cn.quantgroup.tech.brave.configuration;
import brave.Tracer;
import brave.Tracing; import brave.Tracing;
import brave.http.HttpTracing; import brave.http.HttpTracing;
import brave.httpclient.TracingHttpClientBuilder; import brave.httpclient.TracingHttpClientBuilder;
...@@ -15,18 +14,17 @@ import cn.quantgroup.tech.brave.properties.ServiceProperties; ...@@ -15,18 +14,17 @@ import cn.quantgroup.tech.brave.properties.ServiceProperties;
import cn.quantgroup.tech.brave.service.*; import cn.quantgroup.tech.brave.service.*;
import cn.quantgroup.tech.brave.service.impl.*; import cn.quantgroup.tech.brave.service.impl.*;
import cn.quantgroup.tech.brave.slf4j.MDCCurrentTraceContext; import cn.quantgroup.tech.brave.slf4j.MDCCurrentTraceContext;
import com.dangdang.ddframe.job.api.ElasticJob;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.Dispatcher; import okhttp3.Dispatcher;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.*; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -416,32 +414,6 @@ public class BraveAutoConfiguration { ...@@ -416,32 +414,6 @@ public class BraveAutoConfiguration {
return new TechExecutorServiceBuilderTrace(); return new TechExecutorServiceBuilderTrace();
} }
} }
@Aspect
@Configuration
@ConditionalOnClass(ElasticJob.class)
@ConditionalOnProperty(prefix = "tech.brave", name = "enabled", havingValue = "true", matchIfMissing = true)
public class ElasticJobAspect {
@Around("target(com.dangdang.ddframe.job.api.ElasticJob)")
public Object dynamicTrace(ProceedingJoinPoint pjp) throws Throwable {
Tracer tracer = tracing().tracer();
/* 防止应用层非法使用(上下文中存在span),不生成root span。 */
if (tracer.currentSpan() != null) {
return pjp.proceed();
}
brave.Span span = tracer.newTrace();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
// note: try-with-resources closes the scope *before* the catch block
return pjp.proceed();
} catch (RuntimeException | Error e) {
span.error(e);
throw e;
} finally {
span.finish();
}
}
}
} }
} }
......
package cn.quantgroup.tech.brave.job;
import cn.quantgroup.tech.brave.handler.TokenHandler;
import cn.quantgroup.tech.brave.service.ITechRestTemplateBuilder;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Base64;
import java.util.Map;
@Data
class TokenResp {
private Integer code;
@JsonProperty("access_token")
private String token;
@JsonProperty("expires_in")
private Integer expires;
}
@Slf4j
@EnableScheduling
public class TokenJob {
private static final String CREDENTIAL = "credential";
private static final int SUCCESS = 200;
@Value("${tech.auth.clientId}")
private String clientId;
@Value("${tech.auth.secret}")
private String secret;
@Value("${tech.auth.tokenUrl}")
private String tokenUrl;
@Resource
private ITechRestTemplateBuilder techRestTemplateBuilder;
@Bean
public RestTemplate restTemplate() {
return techRestTemplateBuilder.createRestTemplate();
}
@Scheduled(cron = "0 */30 * * * ?")
public void getToken() {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add(CREDENTIAL, base64ForCredential());
HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(requestHeaders);
TokenResp tokenResp = restTemplate().exchange(tokenUrl, HttpMethod.GET, httpEntity, TokenResp.class).getBody();
log.info("getToken tokenResp = 【{}】", tokenResp);
boolean tokenValid = tokenResp != null && SUCCESS == tokenResp.getCode() && StringUtils.isNotBlank(tokenResp.getToken());
if (tokenValid) {
TokenHandler.setToken(tokenResp.getToken());
}
}
@Scheduled(cron = "0 */2 * * * ?")
public void getTokenDemotion() {
if (StringUtils.isBlank(TokenHandler.getToken())) {
getToken();
}
}
private String base64ForCredential() {
String credential = clientId + ":" + secret;
return Base64.getEncoder().encodeToString(credential.getBytes());
}
@PostConstruct
public void init() {
getToken();
}
}
package cn.quantgroup.tech.brave.slf4j; package cn.quantgroup.tech.brave.slf4j;
import brave.internal.HexCodec; import brave.internal.codec.HexCodec;
import brave.internal.Nullable; import brave.internal.Nullable;
import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext;
import brave.propagation.TraceContext; import brave.propagation.TraceContext;
import org.slf4j.MDC; import org.slf4j.MDC;
import static brave.internal.HexCodec.lowerHexEqualsTraceId; import static brave.internal.codec.HexCodec.lowerHexEqualsTraceId;
import static brave.internal.HexCodec.lowerHexEqualsUnsignedLong; import static brave.internal.codec.HexCodec.lowerHexEqualsUnsignedLong;
/** /**
* Adds {@linkplain MDC} properties TRACE_ID, PARENT_ID and SPAN_ID when a {@link * Adds {@linkplain MDC} properties TRACE_ID, PARENT_ID and SPAN_ID when a {@link
......
package cn.quantgroup.tech.autoconfigure;
import cn.quantgroup.tech.endpoint.HealthCheckMvcEndpoint;
import org.springframework.boot.actuate.endpoint.EndpointProperties;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author jinsong.zhu
* @date 2017/12/18
*/
@Configuration
@AutoConfigureAfter({FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class})
@EnableConfigurationProperties(EndpointProperties.class)
public class TechCommonAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public HealthCheckMvcEndpoint healthCheckMvcEndpoint() {
return new HealthCheckMvcEndpoint();
}
}
\ No newline at end of file
...@@ -5,18 +5,7 @@ import org.aspectj.lang.JoinPoint; ...@@ -5,18 +5,7 @@ import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledInfoContributor;
import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/** /**
* @author ag * @author ag
......
...@@ -2,15 +2,10 @@ package cn.quantgroup.tech.db; ...@@ -2,15 +2,10 @@ package cn.quantgroup.tech.db;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware; import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
...@@ -49,10 +44,8 @@ public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, ...@@ -49,10 +44,8 @@ public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar,
public void setEnvironment(Environment environment) { public void setEnvironment(Environment environment) {
//1. 初始化配置. 2. 构建数据源. 3. 设置数据源属性 //1. 初始化配置. 2. 构建数据源. 3. 设置数据源属性
masterDataSource = buildDataSource(initDataSource(environment, MASTER_PREFIX)); masterDataSource = buildDataSource(initDataSource(environment, MASTER_PREFIX));
dataBinder(masterDataSource, environment, MASTER_PREFIX);
slaveDataSource = buildDataSource(initDataSource(environment, SLAVE_PREFIX)); slaveDataSource = buildDataSource(initDataSource(environment, SLAVE_PREFIX));
dataBinder(slaveDataSource, environment, SLAVE_PREFIX);
} }
@Override @Override
...@@ -79,44 +72,17 @@ public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, ...@@ -79,44 +72,17 @@ public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar,
} }
private Map<String, Object> initDataSource(Environment env, String prefix) { private Map<String, Object> initDataSource(Environment env, String prefix) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, prefix);
Map<String, Object> dsMap = new HashMap<>(1); Map<String, Object> dsMap = new HashMap<>(1);
//类型 //类型
dsMap.put("type", propertyResolver.getProperty("type", DATASOURCE_TYPE_DEFAULT)); dsMap.put("type", env.getProperty(prefix + "type", DATASOURCE_TYPE_DEFAULT));
//默认也就是Mysql. 没其他的 //默认也就是Mysql. 没其他的
dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name", DATABASE_TYPE_DEFAULT)); dsMap.put("driver-class-name", env.getProperty(prefix + "driver-class-name", DATABASE_TYPE_DEFAULT));
dsMap.put("url", propertyResolver.getProperty("url")); dsMap.put("url", env.getProperty(prefix + "url"));
dsMap.put("username", propertyResolver.getProperty("username")); dsMap.put("username", env.getProperty(prefix + "username"));
dsMap.put("password", propertyResolver.getProperty("password")); dsMap.put("password", env.getProperty(prefix + "password"));
return dsMap; return dsMap;
} }
/**
* 绑定属性
*/
private void dataBinder(DataSource dataSource, Environment env, String prefix) {
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);
dataBinder.setIgnoreInvalidFields(false);
dataBinder.setIgnoreUnknownFields(true);
Map<String, Object> rpr = new RelaxedPropertyResolver(env, prefix).getSubProperties("");
Map<String, Object> values = new HashMap<>(rpr);
// 排除已经设置的属性
values.remove("type");
values.remove("driver-class-name");
values.remove("url");
values.remove("username");
values.remove("password");
PropertyValues dataSourcePropertyValues = new MutablePropertyValues(values);
dataBinder.bind(dataSourcePropertyValues);
}
/** /**
* 根据已知信息构造DataSource * 根据已知信息构造DataSource
* *
......
package cn.quantgroup.tech.endpoint;
import cn.quantgroup.tech.web.dto.Result;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author jinsong.zhu
* @date 2017/12/18
*/
@ConfigurationProperties(prefix = "endpoints.qg.health")
public class HealthCheckMvcEndpoint implements MvcEndpoint {
public HealthCheckMvcEndpoint() {
}
@RequestMapping(value = "check", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public Result check() {
return Result.ok();
}
@Override
public String getPath() {
return "/health";
}
@Override
public boolean isSensitive() {
return false;
}
@Override
public Class<? extends Endpoint> getEndpointType() {
return null;
}
}
package cn.quantgroup.tech.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 对header中的key进行处理,如果包含用户标识,则处理放入SecurityContext中.
*
* @author jinsong.zhu
* @date 2018/1/16
*/
@Slf4j
public class AuthenticationByGatewayProcessingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// do nothing.
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
ZUser user = TechSecurityUtil.extractHeader(request);
if (user == null) {
// clear context to remove user.
SecurityContextHolder.clearContext();
} else {
Authentication authResult = new ZuulAuthentication(user);
SecurityContextHolder.getContext().setAuthentication(authResult);
}
chain.doFilter(req, res);
}
@Override
public void destroy() {
// do nothing.
}
}
package cn.quantgroup.tech.security;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* 获取当前登录用户.
*
* @author jinsong.zhu
* @date 2018/1/17
*/
public class TechSecurityContext {
/**
* try to get user from security context.
*
* @return return null if has no authenticated user.
*/
public static final ZUser getUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
if (principal instanceof ZUser) {
ZUser user = (ZUser) principal;
return user;
}
return null;
}
public static final Long getUserId() {
ZUser user = getUser();
if (user == null) {
return null;
}
return user.getUserId();
}
public static final String getUserPhoneNo() {
ZUser user = getUser();
if (user == null) {
return null;
}
return user.getPhoneNo();
}
public static final String getUserUuid() {
ZUser user = getUser();
if (user == null) {
return null;
}
return user.getUuid();
}
}
package cn.quantgroup.tech.security;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* @author jinsong.zhu
* @date 2018/1/17
*/
public class TechSecurityUtil {
public static ZUser extractHeader(HttpServletRequest request) {
// valid secret.
String secret = request.getHeader(ZUser.HEADER_SECRET_KEY_FROM_GATEWAY);
if (StringUtils.isEmpty(secret) || !secret.equals(ZUser.HEADER_SECRET_VAL_FROM_GATEWAY)) {
return null;
}
String uuid = request.getHeader(ZUser.HEADER_UUID);
if (StringUtils.isNotEmpty(uuid)) {
String userIdStr = request.getHeader(ZUser.HEADER_USER_ID);
Long userId = parseLong(userIdStr);
if (userId == null) {
// ignore.
return null;
}
String phoneNo = request.getHeader(ZUser.HEADER_PHONE_NO);
if (StringUtils.isEmpty(phoneNo)) {
// ignore.
return null;
}
ZUser user = new ZUser(uuid, userId, phoneNo);
return user;
}
return null;
}
public static Long parseLong(String str) {
return parseLong(str, null);
}
public static Long parseLong(String str, Long defaultValue) {
try {
return Long.parseLong(str);
} catch (Exception e) {
return defaultValue;
}
}
}
package cn.quantgroup.tech.security;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Arrays;
import java.util.Collection;
/**
* 登录用户模型.
* <p>
* @author jinsong.zhu
* @date 2018/1/17
*/
@Data
@NoArgsConstructor
public class ZUser implements UserDetails {
public static final String DEFAULT_PASS = "";
public static final String HEADER_USER_ID = "z-user-id";
public static final String HEADER_UUID = "z-user-uuid";
public static final String HEADER_PHONE_NO = "z-user-phone-no";
public static final String HEADER_SECRET_KEY_FROM_GATEWAY = "z-user-secret";
public static final String HEADER_SECRET_VAL_FROM_GATEWAY = "z.h,w44RXMu4XL#dq_%@ZX,u*gd]zVhmdks@H8krq*Gn-CC:6>YTHX_Kh=_#D7LR";
private String uuid;
private Long userId;
private String phoneNo;
public ZUser(String uuid, Long userId, String phoneNo) {
this.uuid = uuid;
this.userId = userId;
this.phoneNo = phoneNo;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return DEFAULT_PASS;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.uuid != null;
}
}
package cn.quantgroup.tech.security;
import org.springframework.security.authentication.AbstractAuthenticationToken;
/**
* @author jinsong.zhu
* @date 2018/1/17
*/
public class ZuulAuthentication extends AbstractAuthenticationToken {
private static final long serialVersionUID = -4809832298438307319L;
private final ZUser user;
public ZuulAuthentication(ZUser user) {
super(user.getAuthorities());
this.user = user;
}
@Override
public Object getCredentials() {
return "";
}
@Override
public Object getPrincipal() {
return user;
}
@Override
public boolean isAuthenticated() {
return true;
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
package cn.quantgroup.tech.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import redis.clients.jedis.Jedis;
/**
* 目前只写了一个锁, 后面可能还有其他的 Redis 工具类组合
*
* @author zhiguo.liu
* @date 2017/7/28
*/
@Slf4j
public class RedisUtils {
/**
* 通过 setnx + expire 命令,原子性给某个 key 上锁并设置过期时间
* 上锁成功返回 true ,上锁失败返回 false.
*
* @param redisTemplate
* @param key
* @param expire
* @return
*/
public static boolean lock(RedisTemplate redisTemplate, String key, Integer expire) {
RedisConnection connection = null;
try {
connection = redisTemplate.getConnectionFactory().getConnection();
Jedis jedis = (Jedis) connection.getNativeConnection();
return jedis.set(key, "1", "nx", "ex", expire) != null;
} catch (Exception e) {
log.error("上锁出错:{}", e);
} finally {
if (connection != null) {
connection.close();
}
}
return false;
}
}
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
<brave.version>5.13.7</brave.version> <brave.version>5.13.7</brave.version>
<zipkin-reporter2.version>2.16.3</zipkin-reporter2.version> <zipkin-reporter2.version>2.16.3</zipkin-reporter2.version>
<zipkin-kafka.version>2.8.2</zipkin-kafka.version>
<zipkin.version>2.23.16</zipkin.version> <zipkin.version>2.23.16</zipkin.version>
<okhttp.version>4.9.3</okhttp.version> <okhttp.version>4.9.3</okhttp.version>
<transmittable-thread-local.version>2.12.4</transmittable-thread-local.version> <transmittable-thread-local.version>2.12.4</transmittable-thread-local.version>
...@@ -39,7 +40,6 @@ ...@@ -39,7 +40,6 @@
<modules> <modules>
<module>commons-spring</module> <module>commons-spring</module>
<module>shutdown-spring-boot-starter</module>
<module>brave-spring-boot-starter</module> <module>brave-spring-boot-starter</module>
<module>idgenerator-spring-boot-starter</module> <module>idgenerator-spring-boot-starter</module>
<module>enoch-agent-spring-boot-starter</module> <module>enoch-agent-spring-boot-starter</module>
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
<dependency> <dependency>
<groupId>io.zipkin.reporter2</groupId> <groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-kafka11</artifactId> <artifactId>zipkin-sender-kafka11</artifactId>
<version>${zipkin-reporter2.version}</version> <version>${zipkin-kafka.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.zipkin.brave</groupId> <groupId>io.zipkin.brave</groupId>
......
<?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">
<parent>
<artifactId>commons-parent</artifactId>
<groupId>cn.quantgroup</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shutdown-spring-boot-starter</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package cn.quantgroup.tech.shutdown;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.config.RabbitListenerConfigUtils;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.ApplicationContext;
import org.springframework.context.Lifecycle;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* 这里是一些基本的停止工具. to be continue
*
* @author ag
*/
@Slf4j
@Configuration
public class BaseDestroyHandler {
@Configuration
@ConditionalOnClass(RabbitListenerEndpointRegistry.class)
public static class DestroyMessageQueueListener {
@Autowired
private ApplicationContext applicationContext;
private RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry;
@PostConstruct
public void initMQConf() {
try {
rabbitListenerEndpointRegistry = applicationContext.getBean(
RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME,
RabbitListenerEndpointRegistry.class);
} catch (Exception e) {
log.warn("注入MQ endpoint registry 失败, 如果你有MQ队列接收, 可能你要关注一下了");
}
}
@PreDestroy
private void stopRabbitMQ() {
try {
if (rabbitListenerEndpointRegistry == null) {
log.warn("我什么都没做, MQ listener stopped");
return;
}
rabbitListenerEndpointRegistry.getListenerContainers().forEach(Lifecycle::stop);
} catch (Exception e) {
//貌似这段日志不会打印...
log.error("貌似停止 MQ 遇到了问题... 你有MQ么?", e);
}
log.info("MQ listener stopped");
}
}
@PreDestroy
private void stopOther() {
log.info("我什么都没做, other stopped");
}
}
package cn.quantgroup.tech.shutdown;
import cn.quantgroup.tech.shutdown.properties.GracefulShutdownProperties;
import cn.quantgroup.tech.shutdown.service.Shutdown;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import sun.misc.Signal;
import sun.misc.SignalHandler;
/**
* 默认的停止信号接收处理.
*
* @author ag
*/
@Slf4j
public class DefaultSignalHandler implements SignalHandler {
private ApplicationContext context;
public DefaultSignalHandler(ApplicationContext context) {
this.context = context;
}
@Override
public void handle(Signal signal) {
try {
String applicationName = context.getApplicationName();
log.info("开始执行停止{}服务", applicationName);
GracefulShutdownProperties bean = context.getBean(GracefulShutdownProperties.class);
String shutdownBeanName = null;
try {
String[] shutdownBeanNames = context.getBeanNamesForType(Shutdown.class);
shutdownBeanName = shutdownBeanNames[0];
//如果不是web应用. 这里会找不到shutdown bean
context.getBean(shutdownBeanName, Shutdown.class).shutdown(bean.getTimeout());
log.info("{} 停止接收请求", shutdownBeanName);
} catch (Exception e) {
log.info("{} shutdown 失败", shutdownBeanName);
}
log.info("{} 即将执行 @PreDestroy 方法", applicationName);
System.exit(0);
//处理无法exit(0)的动作
Thread.getAllStackTraces().forEach((thread, stackTraceElements) -> {
if (!thread.isDaemon()) {
//如果中断 daemon 线程, 会导致 PreDestroy 方法不执行,
//如果不中断 non-daemon 线程, 会导致无法 exit(0).
log.debug("中断正在进行的 non-daemon 线程: {} ; {}", thread.getId(), thread.getName());
thread.interrupt();
}
});
System.exit(0);
} catch (Exception e) {
// 此处可能导致异常的里面包含了logback中类未能找到, 所以增加输出到控制台.
System.out.println(e.getMessage());
log.error(e.getMessage(), e);
} finally {
System.out.println("强制退出.");
System.exit(1);
}
}
}
package cn.quantgroup.tech.shutdown.configuration;
import cn.quantgroup.tech.shutdown.DefaultSignalHandler;
import cn.quantgroup.tech.shutdown.properties.GracefulShutdownProperties;
import cn.quantgroup.tech.shutdown.service.JettyShutdown;
import cn.quantgroup.tech.shutdown.service.TomcatShutdown;
import cn.quantgroup.tech.shutdown.service.UndertowShutdown;
import cn.quantgroup.tech.shutdown.wrapper.UndertowShutdownHandlerWrapper;
import io.undertow.Undertow;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.startup.Tomcat;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.undertow.UndertowDeploymentInfoCustomizer;
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.xnio.SslClientAuthMode;
import sun.misc.Signal;
import javax.servlet.Servlet;
/**
* This configuration class will be picked up by Spring Boot's auto configuration capabilities as soon as it's
* on the classpath.
*/
@Slf4j
@Configuration
@ConditionalOnWebApplication
@ConditionalOnProperty(prefix = "shutdown.graceful", name = "enabled", havingValue = "true", matchIfMissing = true)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@EnableConfigurationProperties(GracefulShutdownProperties.class)
@Import(EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar.class)
public class GracefulShutdownAutoConfiguration {
ConfigurableApplicationContext applicationContext;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
log.info("注册对 Signal INT 信号的接收.");
Signal.handle(new Signal("INT"), new DefaultSignalHandler(this.applicationContext));
}
/**
* Configuration for Tomcat.
*/
@Configuration
@ConditionalOnClass({Servlet.class, Tomcat.class})
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatShutdown tomcatShutdown() {
log.info("容器是 tomcat,成功构造 shutdown hook.");
return new TomcatShutdown();
}
/**
* Customise the tomcat factory.
*
* @return an EmbeddedServletContainerCustomizer
*/
@Bean
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
return container -> {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
((TomcatEmbeddedServletContainerFactory) container).addConnectorCustomizers(tomcatShutdown());
}
};
}
}
@Configuration
@ConditionalOnClass({Servlet.class, Server.class, Loader.class,
WebAppContext.class})
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyShutdown jettyShutdown() {
log.info("容器是 jetty,成功构造 shutdown hook.");
return new JettyShutdown();
}
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
@Bean
public EmbeddedServletContainerCustomizer jettyCustomizer() {
return container -> {
if (container instanceof JettyEmbeddedServletContainerFactory) {
((JettyEmbeddedServletContainerFactory) container).addServerCustomizers(jettyShutdown());
}
};
}
}
/**
* Configuration for Undertow.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowShutdown undertowShutdown() {
log.info("容器是 undertow,成功构造 shutdown hook.");
return new UndertowShutdown();
}
/**
* Customise the undertow factory.
*
* @return an EmbeddedServletContainerCustomizer
*/
@Bean
public EmbeddedServletContainerCustomizer undertowCustomizer() {
return container -> {
if (container instanceof UndertowEmbeddedServletContainerFactory) {
((UndertowEmbeddedServletContainerFactory) container).addDeploymentInfoCustomizers(undertowDeploymentInfoCustomizer());
}
};
}
@Bean
public UndertowDeploymentInfoCustomizer undertowDeploymentInfoCustomizer() {
return deploymentInfo -> deploymentInfo.addOuterHandlerChainWrapper(undertowShutdownHandlerWrapper());
}
@Bean
public UndertowShutdownHandlerWrapper undertowShutdownHandlerWrapper() {
return new UndertowShutdownHandlerWrapper();
}
}
}
package cn.quantgroup.tech.shutdown.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Global graceful shutdown properties.
*
* @author Corentin Azelart
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "shutdown.graceful")
public class GracefulShutdownProperties {
/**
* The timeout before force shutdown. TimeUnit.Second
*/
private Integer timeout = 10;
}
package cn.quantgroup.tech.shutdown.service;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
/**
* @author ag
*/
@Slf4j
public class JettyShutdown implements Shutdown, JettyServerCustomizer {
private volatile Server server;
@Override
public void shutdown(Integer delay) throws InterruptedException {
StatisticsHandler handler = new StatisticsHandler();
handler.setHandler(server.getHandler());
server.setHandler(handler);
server.setStopTimeout(delay);
//todo stop at shutdown
server.setStopAtShutdown(true);
}
@Override
public void customize(Server server) {
this.server = server;
}
}
package cn.quantgroup.tech.shutdown.service;
/**
* Shutdown service.
*/
public interface Shutdown {
/**
* Perform shutdown.
* @param delay is delay to force
* @throw InterruptedException if we have an interruption
*/
void shutdown(Integer delay) throws InterruptedException;
}
package cn.quantgroup.tech.shutdown.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* Perform a tomcat shutdown.
*/
@Slf4j
public class TomcatShutdown implements Shutdown, TomcatConnectorCustomizer {
/**
* Implementation of a Coyote connector.
*/
private volatile Connector connector;
/**
* Perform a shutdown
*
* @param delay is delay to force is the delay before perform a force shutdown
* @throws InterruptedException if we have an exception
*/
@Override
public void shutdown(Integer delay) throws InterruptedException {
// Used to properly handle the work queue.
final Executor executor = connector.getProtocolHandler().getExecutor();
final ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
/*
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
*/
threadPoolExecutor.shutdown();
// We wait after the end of the current requests
if (!threadPoolExecutor.awaitTermination(delay, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within " + delay + " second(s). Proceeding with force shutdown");
} else {
log.debug("Tomcat thread pool is empty, we stop now");
}
}
/**
* Set connector.
*
* @param connector is the catalina connector.
*/
@Override
public void customize(final Connector connector) {
this.connector = connector;
}
}
package cn.quantgroup.tech.shutdown.service;
import cn.quantgroup.tech.shutdown.wrapper.UndertowShutdownHandlerWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Undertow shutdown.
*/
@Slf4j
public class UndertowShutdown implements Shutdown {
/**
* The wrapper for manual commands.
*/
@Autowired
private UndertowShutdownHandlerWrapper undertowShutdownHandlerWrapper;
/**
* Perform shutdown.
*
* @param delay is delay to force
* @throw InterruptedException if we have an interruption
*/
@Override
public void shutdown(Integer delay) throws InterruptedException {
undertowShutdownHandlerWrapper.getGracefulShutdownHandler().shutdown();
undertowShutdownHandlerWrapper.getGracefulShutdownHandler().awaitShutdown(delay * 1000);
}
}
package cn.quantgroup.tech.shutdown.wrapper;
import io.undertow.server.HandlerWrapper;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.GracefulShutdownHandler;
/**
* Undertow handler wrapper.
*/
public class UndertowShutdownHandlerWrapper implements HandlerWrapper {
/**
* graceful shutdown handler.
*/
private GracefulShutdownHandler gracefulShutdownHandler;
/**
* Wrapper.
* @param handler is the http handler from chain.
* @return the Undertown shutdown handler.
*/
@Override
public HttpHandler wrap(final HttpHandler handler) {
if(gracefulShutdownHandler == null) {
this.gracefulShutdownHandler = new GracefulShutdownHandler(handler);
}
return gracefulShutdownHandler;
}
/**
* Return the graceful shutdown handler to perform manual command : pause/shutdown.
* @return the shutdown handler.
*/
public GracefulShutdownHandler getGracefulShutdownHandler() {
return gracefulShutdownHandler;
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.quantgroup.tech.shutdown.configuration.GracefulShutdownAutoConfiguration
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment