Initial commit
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0"?>
|
||||
<project
|
||||
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" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-microservice-common</artifactId>
|
||||
<name>lingniu-framework-plugin-microservice-common</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hystrix</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
<version>3.1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
<version>1.3.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-context</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.lingniu.framework.plugin.microservice.common.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@Data
|
||||
public class DefaultHttpProperties {
|
||||
|
||||
/**
|
||||
* 每个route默认的最大连接数
|
||||
*/
|
||||
private Integer defaultMaxPerRoute = 25;
|
||||
/**
|
||||
* 从连接池中获取连接的超时时间,超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
|
||||
*/
|
||||
private Integer connectionRequestTimeout = 500;
|
||||
/**
|
||||
* 空闲永久连接检查间隔
|
||||
*/
|
||||
private Integer validateAfterInactivity = 2000;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.lingniu.framework.plugin.microservice.common.config;
|
||||
|
||||
import feign.Logger;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@Data
|
||||
public class FeignProperties {
|
||||
|
||||
private Logger.Level loggerLevel = Logger.Level.BASIC;
|
||||
|
||||
private Long period = 500L;
|
||||
|
||||
private Integer maxPeriod = 1000;
|
||||
|
||||
private Integer maxAttempts = 1;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.lingniu.framework.plugin.microservice.common.config;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@Data
|
||||
@ConfigurationProperties(MircroServiceConfig.PRE_FIX)
|
||||
public class MircroServiceConfig {
|
||||
|
||||
public static final String PRE_FIX = "framework.lingniu.spring.cloud";
|
||||
|
||||
/**
|
||||
* 是否启用OkHttpClient
|
||||
*/
|
||||
private Boolean isOkHttp = true;
|
||||
/**
|
||||
* common
|
||||
* 读取超时
|
||||
*/
|
||||
private Integer readTimeout = 1000;
|
||||
/**
|
||||
* common
|
||||
* 连接超时
|
||||
*/
|
||||
private Integer connectTimeout = 1000;
|
||||
/**
|
||||
* common
|
||||
* 整个连接池的最大连接数
|
||||
*/
|
||||
private Integer maxTotal = 150;
|
||||
/**
|
||||
* fegin配置
|
||||
*/
|
||||
private FeignProperties feign = new FeignProperties();
|
||||
/**
|
||||
* defaultHttp. 默认http配置
|
||||
*/
|
||||
private DefaultHttpProperties defaultHttp = new DefaultHttpProperties();
|
||||
/**
|
||||
* okHttp相关配置
|
||||
*/
|
||||
private OkHttpProperties okHttp = new OkHttpProperties();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.lingniu.framework.plugin.microservice.common.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Data
|
||||
public class OkHttpProperties {
|
||||
|
||||
/**
|
||||
* OkHttp TimeToLive
|
||||
*/
|
||||
private Long timeToLive = 2000L;
|
||||
/**
|
||||
* 时间单位
|
||||
*/
|
||||
private TimeUnit timeToLiveUnit = TimeUnit.MILLISECONDS;
|
||||
/**
|
||||
* 是否开启重定身
|
||||
*/
|
||||
private Boolean isFollowRedirects = true;
|
||||
/**
|
||||
* 是否开启SSL验证
|
||||
*/
|
||||
private Boolean disableSslValidation = false;
|
||||
/**
|
||||
* 写起时
|
||||
*/
|
||||
private Integer writeTimeout = 1000;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.lingniu.framework.plugin.microservice.common.core;
|
||||
|
||||
|
||||
import cn.lingniu.framework.plugin.core.base.CommonResult;
|
||||
|
||||
/**
|
||||
* @description: 异常解码器
|
||||
**/
|
||||
public interface ErrorCommonResultHandler {
|
||||
|
||||
CommonResult handler(String body);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package cn.lingniu.framework.plugin.microservice.common.core;
|
||||
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HttpMessageConverter} that can read and write strings.
|
||||
*
|
||||
* <p>By default, this converter supports all media types ({@code */*}),
|
||||
* and writes with a {@code Content-Type} of {@code text/plain}. This can be overridden
|
||||
* by setting the {@link #setSupportedMediaTypes supportedMediaTypes} property.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
|
||||
@Nullable
|
||||
private volatile List<Charset> availableCharsets;
|
||||
|
||||
private boolean writeAcceptCharset = true;
|
||||
|
||||
|
||||
/**
|
||||
* A default constructor that uses {@code "ISO-8859-1"} as the default charset.
|
||||
*
|
||||
* @see #StringHttpMessageConverter(Charset)
|
||||
*/
|
||||
public StringHttpMessageConverter() {
|
||||
this(DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor accepting a default charset to use if the requested content
|
||||
* type does not specify one.
|
||||
*/
|
||||
public StringHttpMessageConverter(Charset defaultCharset) {
|
||||
super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indicates whether the {@code Accept-Charset} should be written to any outgoing request.
|
||||
* <p>Default is {@code true}.
|
||||
*/
|
||||
public void setWriteAcceptCharset(boolean writeAcceptCharset) {
|
||||
this.writeAcceptCharset = writeAcceptCharset;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return String.class == clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
|
||||
Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
|
||||
return StreamUtils.copyToString(inputMessage.getBody(), charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long getContentLength(String str, @Nullable MediaType contentType) {
|
||||
Charset charset = getContentTypeCharset(contentType);
|
||||
return (long) str.getBytes(charset).length;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
|
||||
if (this.writeAcceptCharset) {
|
||||
outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
|
||||
}
|
||||
Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
|
||||
StreamUtils.copy(str, charset, outputMessage.getBody());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the list of supported {@link Charset}s.
|
||||
* <p>By default, returns {@link Charset#availableCharsets()}.
|
||||
* Can be overridden in subclasses.
|
||||
*
|
||||
* @return the list of accepted charsets
|
||||
*/
|
||||
protected List<Charset> getAcceptedCharsets() {
|
||||
List<Charset> charsets = this.availableCharsets;
|
||||
if (charsets == null) {
|
||||
charsets = new ArrayList<>(Charset.availableCharsets().values());
|
||||
this.availableCharsets = charsets;
|
||||
}
|
||||
return charsets;
|
||||
}
|
||||
|
||||
private Charset getContentTypeCharset(@Nullable MediaType contentType) {
|
||||
if (contentType != null && contentType.getCharset() != null) {
|
||||
return contentType.getCharset();
|
||||
} else {
|
||||
Charset charset = getDefaultCharset();
|
||||
Assert.state(charset != null, "No default charset");
|
||||
return charset;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright © 2015-2026 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.lingniu.framework.plugin.microservice.common.core.decoder;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.base.CommonResult;
|
||||
import cn.lingniu.framework.plugin.core.exception.ServerException;
|
||||
import cn.lingniu.framework.plugin.core.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.lingniu.framework.plugin.microservice.common.core.ErrorCommonResultHandler;
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import feign.Response;
|
||||
import feign.Util;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* 异常解析
|
||||
*/
|
||||
@Slf4j
|
||||
public class LingniuErrorDecoder extends ErrorDecoder.Default implements ErrorDecoder {
|
||||
|
||||
private final ErrorCommonResultHandler handler;
|
||||
|
||||
public LingniuErrorDecoder(ErrorCommonResultHandler errorCommonResultHandler) {
|
||||
this.handler = errorCommonResultHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exception decode(final String methodKey, final Response response) {
|
||||
try {
|
||||
if (HttpStatus.NOT_FOUND.value() == response.status() || ObjectEmptyUtils.isEmpty(response.body())) {
|
||||
return new ServerException(GlobalErrorCodeConstants.NOT_FOUND);
|
||||
} else if (HttpStatus.UNAUTHORIZED.value() == response.status()) {
|
||||
return new ServerException(GlobalErrorCodeConstants.UNAUTHORIZED);
|
||||
} else if (HttpStatus.METHOD_NOT_ALLOWED.value() == response.status()) {
|
||||
return new ServerException(GlobalErrorCodeConstants.FORBIDDEN);
|
||||
} else if (HttpStatus.TOO_MANY_REQUESTS.value() == response.status()) {
|
||||
return new ServerException(GlobalErrorCodeConstants.TOO_MANY_REQUESTS);
|
||||
} else if (HttpStatus.REQUEST_TIMEOUT.value() == response.status() || HttpStatus.GATEWAY_TIMEOUT.value() == response.status()) {
|
||||
return new ServerException(GlobalErrorCodeConstants.TIME_OUT_ERROR);
|
||||
} else if (HttpStatus.BAD_GATEWAY.value() == response.status()) {
|
||||
return new ServerException(GlobalErrorCodeConstants.BAD_GATE_WAY_ERROR);
|
||||
}
|
||||
String body = Util.toString(response.body().asReader());
|
||||
CommonResult result = handler.handler(body);
|
||||
if (ObjectEmptyUtils.isNotEmpty(result) && result.getCode() != 0) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("微服务调用: {} 响应异常信息:{} {} ", methodKey, result.getCode(), result.getMsg());
|
||||
}
|
||||
return new ServerException(result.getCode(), result.getMsg());
|
||||
} else if (HttpStatus.valueOf(response.status()).is5xxServerError()) {
|
||||
return new ServerException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR);
|
||||
} else {
|
||||
return new ServerException(GlobalErrorCodeConstants.UNKNOWN);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("Feign异常解码出现问题", ex);
|
||||
return new ServerException(GlobalErrorCodeConstants.FEIGN_DECODE_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright © 2015-2026 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.lingniu.framework.plugin.microservice.common.core.decoder;
|
||||
|
||||
import feign.FeignException;
|
||||
import feign.Response;
|
||||
import feign.codec.Decoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* LingniuFeignDecoder
|
||||
*/
|
||||
@Slf4j
|
||||
public class LingniuFeignDecoder implements Decoder {
|
||||
|
||||
private Decoder decoder;
|
||||
|
||||
public LingniuFeignDecoder(final Decoder decoder) {
|
||||
this.decoder = decoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(final Response response, final Type t) throws IOException, FeignException {
|
||||
Type type = t;
|
||||
if (isParameterizeHttpEntity(type)) {
|
||||
type = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||
Object decodedObject = decoder.decode(response, type);
|
||||
return createResponse(decodedObject, response);
|
||||
} else if (isHttpEntity(type)) {
|
||||
return createResponse(null, response);
|
||||
} else {
|
||||
Object decodeResponse = decoder.decode(response, type);
|
||||
return decodeResponse;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isParameterizeHttpEntity(final Type type) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
return isHttpEntity(((ParameterizedType) type).getRawType());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private boolean isHttpEntity(final Type type) {
|
||||
if (type instanceof Class) {
|
||||
Class c = (Class) type;
|
||||
return HttpEntity.class.isAssignableFrom(c);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> ResponseEntity<T> createResponse(final Object instance, final Response response) {
|
||||
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
|
||||
for (String key : response.headers().keySet()) {
|
||||
headers.put(key, new LinkedList<>(response.headers().get(key)));
|
||||
}
|
||||
return new ResponseEntity<>((T) instance, headers, HttpStatus.valueOf(response.status()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package cn.lingniu.framework.plugin.microservice.common.core.interceptor;
|
||||
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class CharlesRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static ObjectMapper objectMapper;
|
||||
|
||||
static {
|
||||
objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
// feign 不支持 GET 方法传 POJO, json body转query
|
||||
if (template.method().equals("GET") && ObjectEmptyUtils.isNotEmpty(template.body())) {
|
||||
try {
|
||||
JsonNode jsonNode = objectMapper.readTree(template.body());
|
||||
template.body(null, Charset.defaultCharset());
|
||||
Map<String, Collection<String>> queries = new HashMap<>();
|
||||
buildQuery(jsonNode, "", queries);
|
||||
template.queries(queries);
|
||||
} catch (IOException e) {
|
||||
log.error("CharlesRequestInterceptor异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String encoderParameters(String parameterValue) {
|
||||
try {
|
||||
return URLEncoder.encode(parameterValue, "utf-8");
|
||||
} catch (Exception ex) {
|
||||
log.error("参数转换异常", ex);
|
||||
}
|
||||
return parameterValue;
|
||||
}
|
||||
|
||||
private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {
|
||||
if (!jsonNode.isContainerNode()) {
|
||||
if (jsonNode.isNull()) {
|
||||
return;
|
||||
}
|
||||
Collection<String> values = queries.get(path);
|
||||
if (null == values) {
|
||||
values = new ArrayList<>();
|
||||
queries.put(path, values);
|
||||
}
|
||||
values.add(encoderParameters(jsonNode.asText()));
|
||||
return;
|
||||
}
|
||||
if (jsonNode.isArray()) {
|
||||
Iterator<JsonNode> it = jsonNode.elements();
|
||||
while (it.hasNext()) {
|
||||
buildQuery(it.next(), path, queries);
|
||||
}
|
||||
} else {
|
||||
Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = it.next();
|
||||
if (ObjectEmptyUtils.isNotEmpty(path)) {
|
||||
buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
|
||||
} else {
|
||||
buildQuery(entry.getValue(), entry.getKey(), queries);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.lingniu.framework.plugin.microservice.common.core.interceptor;
|
||||
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class FeignLoggerInterceptor implements RequestInterceptor {
|
||||
|
||||
public FeignLoggerInterceptor() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
String bodyContent = ObjectEmptyUtils.isNotEmpty(requestTemplate.body()) ? new String(requestTemplate.body()) : "";
|
||||
log.info("Feign调用日志:URL:{} Mehod:{} Body:{} ", requestTemplate.url(), requestTemplate.method(), bodyContent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.lingniu.framework.plugin.microservice.common.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.microservice.common.config.MircroServiceConfig;
|
||||
import feign.Client;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.config.Registry;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.socket.ConnectionSocketFactory;
|
||||
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* DefaultFeignLoadBalancedConfiguration
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "framework.lingniu.spring.cloud.isOkHttp", havingValue = "false")
|
||||
class DefaultFeignLoadBalancedConfiguration {
|
||||
|
||||
|
||||
@Bean
|
||||
public Client feignClient(MircroServiceConfig mircroServiceConfig,
|
||||
LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
ApacheHttpClient delegate = new ApacheHttpClient(httpClient(mircroServiceConfig));
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate,
|
||||
loadBalancerClient, loadBalancedRetryFactory, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
|
||||
public HttpClient httpClient(MircroServiceConfig mircroServiceConfig) {
|
||||
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
|
||||
.register("http", PlainConnectionSocketFactory.getSocketFactory())
|
||||
.register("https", SSLConnectionSocketFactory.getSocketFactory())
|
||||
.build();
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
|
||||
connectionManager.setMaxTotal(mircroServiceConfig.getMaxTotal());
|
||||
connectionManager.setDefaultMaxPerRoute(mircroServiceConfig.getDefaultHttp().getDefaultMaxPerRoute());
|
||||
connectionManager.setValidateAfterInactivity(mircroServiceConfig.getDefaultHttp().getValidateAfterInactivity());
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setSocketTimeout(mircroServiceConfig.getReadTimeout())
|
||||
.setConnectTimeout(mircroServiceConfig.getConnectTimeout())
|
||||
.setConnectionRequestTimeout(mircroServiceConfig.getDefaultHttp().getConnectionRequestTimeout())
|
||||
.build();
|
||||
return HttpClientBuilder.create()
|
||||
.setDefaultRequestConfig(requestConfig)
|
||||
.setConnectionManager(connectionManager)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package cn.lingniu.framework.plugin.microservice.common.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.microservice.common.config.MircroServiceConfig;
|
||||
import cn.lingniu.framework.plugin.microservice.common.core.StringHttpMessageConverter;
|
||||
import cn.lingniu.framework.plugin.microservice.common.core.decoder.LingniuFeignDecoder;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
||||
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import feign.form.FormEncoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.cloud.openfeign.support.SpringDecoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@ConditionalOnClass(name = {"com.alibaba.fastjson.support.config.FastJsonConfig"})
|
||||
@ConditionalOnProperty(prefix = MircroServiceConfig.PRE_FIX, name = "json-converter", havingValue = "fastjson")
|
||||
@Configuration
|
||||
@AutoConfigureBefore({LingniuCloudAutoConfiguration.class})
|
||||
public class HttpMessageFastJsonConverterConfiguration {
|
||||
|
||||
HttpMessageConverters converters = new HttpMessageConverters(createStringConverter(), createFastJsonConverter());
|
||||
|
||||
@Bean
|
||||
public Decoder fastJsonFeignDecoder() {
|
||||
return new LingniuFeignDecoder(new SpringDecoder(() -> converters));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Encoder fastJsonFeignEncoder() {
|
||||
return new FormEncoder(new SpringEncoder(() -> converters));
|
||||
}
|
||||
|
||||
private HttpMessageConverter createStringConverter() {
|
||||
//fastConverter转换纯String有问题,该处使用的是重写后的转换器,防止被Spring优先级排到后面导致没有生效
|
||||
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
|
||||
stringHttpMessageConverter.setWriteAcceptCharset(false);
|
||||
return stringHttpMessageConverter;
|
||||
}
|
||||
|
||||
private HttpMessageConverter createFastJsonConverter() {
|
||||
//创建fastJson消息转换器
|
||||
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
|
||||
//升级最新版本需加=============================================================
|
||||
List<MediaType> supportedMediaTypes = new ArrayList<>();
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_PDF);
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_XML);
|
||||
supportedMediaTypes.add(MediaType.IMAGE_GIF);
|
||||
supportedMediaTypes.add(MediaType.IMAGE_JPEG);
|
||||
supportedMediaTypes.add(MediaType.IMAGE_PNG);
|
||||
supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
|
||||
supportedMediaTypes.add(MediaType.TEXT_HTML);
|
||||
supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
|
||||
supportedMediaTypes.add(MediaType.TEXT_PLAIN);
|
||||
supportedMediaTypes.add(MediaType.TEXT_XML);
|
||||
fastConverter.setSupportedMediaTypes(supportedMediaTypes);
|
||||
|
||||
//创建配置类
|
||||
FastJsonConfig fastJsonConfig = new FastJsonConfig();
|
||||
//修改配置返回内容的过滤
|
||||
//WriteNullListAsEmpty :List字段如果为null,输出为[],而非null
|
||||
//WriteNullStringAsEmpty : 字符类型字段如果为null,输出为"",而非null
|
||||
//DisableCircularReferenceDetect :消除对同一对象循环引用的问题,默认为false(如果不配置有可能会进入死循环)
|
||||
//WriteNullBooleanAsFalse:Boolean字段如果为null,输出为false,而非null
|
||||
//WriteMapNullValue:是否输出值为null的字段,默认为false
|
||||
fastJsonConfig.setSerializerFeatures(
|
||||
SerializerFeature.DisableCircularReferenceDetect,
|
||||
SerializerFeature.WriteMapNullValue
|
||||
);
|
||||
fastConverter.setFastJsonConfig(fastJsonConfig);
|
||||
|
||||
return fastConverter;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright © 2015-2026 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.lingniu.framework.plugin.microservice.common.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.base.CommonResult;
|
||||
import cn.lingniu.framework.plugin.core.config.CommonConstant;
|
||||
import cn.lingniu.framework.plugin.microservice.common.config.MircroServiceConfig;
|
||||
import cn.lingniu.framework.plugin.microservice.common.core.ErrorCommonResultHandler;
|
||||
import cn.lingniu.framework.plugin.microservice.common.core.decoder.LingniuErrorDecoder;
|
||||
import cn.lingniu.framework.plugin.microservice.common.core.decoder.LingniuFeignDecoder;
|
||||
import cn.lingniu.framework.plugin.microservice.common.core.interceptor.CharlesRequestInterceptor;
|
||||
import cn.lingniu.framework.plugin.microservice.common.core.interceptor.FeignLoggerInterceptor;
|
||||
import cn.lingniu.framework.plugin.util.json.JsonUtil;
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import cn.lingniu.framework.plugin.web.config.FrameworkWebConfig;
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.Request;
|
||||
import feign.Retryer;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import feign.form.FormEncoder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
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.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.SpringDecoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* LingniuCloudAutoConfiguration配置
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
@EnableFeignClients(basePackages = {"cn.lingniu.framework.plugin.microservice.common", "cn.lingniu"})
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
@EnableConfigurationProperties({MircroServiceConfig.class})
|
||||
@AutoConfigureAfter(FrameworkWebConfig.class)
|
||||
@RequiredArgsConstructor
|
||||
@Import({OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class, HttpMessageFastJsonConverterConfiguration.class})
|
||||
public class LingniuCloudAutoConfiguration implements EnvironmentAware {
|
||||
|
||||
private static final long PERIOD = 500L;
|
||||
private static final int MAX_PERIOD = 1000;
|
||||
private static final int MAX_ATTEMPTS = 2;
|
||||
private final ObjectFactory<HttpMessageConverters> messageConverters;
|
||||
private final MircroServiceConfig mircroServiceConfig;
|
||||
private final FrameworkWebConfig frameworkWebConfig;
|
||||
private Environment environment;
|
||||
|
||||
@Bean
|
||||
public ErrorDecoder getLingniuErrorDecoder(ErrorCommonResultHandler errorCommonResultHandler) {
|
||||
return new LingniuErrorDecoder(errorCommonResultHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = MircroServiceConfig.PRE_FIX, name = "json-converter", havingValue = "jackson", matchIfMissing = true)
|
||||
public Decoder getLingniuFeignDecoder() {
|
||||
return new LingniuFeignDecoder(new SpringDecoder(messageConverters));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = MircroServiceConfig.PRE_FIX, name = "json-converter", havingValue = "jackson", matchIfMissing = true)
|
||||
public Encoder getFormEncoder() {
|
||||
return new FormEncoder(new SpringEncoder(messageConverters));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ErrorCommonResultHandler errorHandler() {
|
||||
return (b) -> JsonUtil.json2Bean(b, CommonResult.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CharlesRequestInterceptor charlesRequestInterceptor() {
|
||||
return new CharlesRequestInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FeignLoggerInterceptor headersInterceptor() {
|
||||
return new FeignLoggerInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* feignBuilder创建
|
||||
*/
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
public Feign.Builder feignBuilder(ErrorCommonResultHandler handler, Optional<Retryer> retryer, Client restClient, Request.Options options) {
|
||||
Feign.Builder builder = Feign.builder()
|
||||
.errorDecoder(new LingniuErrorDecoder(handler))
|
||||
.options(options)
|
||||
.logLevel(mircroServiceConfig.getFeign().getLoggerLevel())
|
||||
.retryer(retryer.isPresent() ? retryer.get() : Retryer.NEVER_RETRY)
|
||||
.client(restClient);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Request.Options options() {
|
||||
return new Request.Options(mircroServiceConfig.getConnectTimeout(), mircroServiceConfig.getReadTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* todo 后续可扩展
|
||||
* 创建 RestTemplate 实例
|
||||
* @param restTemplateBuilder {@link RestTemplateAutoConfiguration#restTemplateBuilder}
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@LoadBalanced
|
||||
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
|
||||
return restTemplateBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* feignRetryer实例化
|
||||
*
|
||||
* @return feignRetryer实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = MircroServiceConfig.PRE_FIX, name = "feign-retryer", havingValue = "true", matchIfMissing = false)
|
||||
public Retryer feignRetryer() {
|
||||
return new Retryer.Default(ObjectEmptyUtils.isEmpty(mircroServiceConfig.getFeign().getPeriod()) ? PERIOD : mircroServiceConfig.getFeign().getPeriod(),
|
||||
ObjectEmptyUtils.isEmpty(mircroServiceConfig.getFeign().getMaxPeriod()) ? MAX_PERIOD : mircroServiceConfig.getFeign().getMaxPeriod(),
|
||||
ObjectEmptyUtils.isEmpty(mircroServiceConfig.getFeign().getMaxAttempts()) ? MAX_ATTEMPTS : mircroServiceConfig.getFeign().getMaxAttempts());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.lingniu.framework.plugin.microservice.common.init;
|
||||
|
||||
|
||||
import cn.lingniu.framework.plugin.util.config.PropertyUtils;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
|
||||
@Order(Integer.MIN_VALUE + 500)
|
||||
public class LingniuCloudInit implements org.springframework.context.ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
ConfigurableEnvironment environment = applicationContext.getEnvironment();
|
||||
PropertyUtils.setDefaultInitProperty("spring.main.allow-bean-definition-overriding", "true");
|
||||
PropertyUtils.setDefaultInitProperty("spring.autoconfigure.exclude[0]", "com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure");
|
||||
PropertyUtils.setDefaultInitProperty("spring.autoconfigure.exclude[1]", "org.springframework.boot.actuate.autoconfigure.redis.RedisHealthIndicatorAutoConfiguration");
|
||||
PropertyUtils.setDefaultInitProperty("spring.autoconfigure.exclude[2]", "org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthIndicatorAutoConfiguration");
|
||||
PropertyUtils.setDefaultInitProperty("spring.autoconfigure.exclude[3]", "org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticSearchRestHealthIndicatorAutoConfiguration");
|
||||
if (environment.getProperty("framework.lingniu.spring.cloud.isOkHttp", "true").equalsIgnoreCase(Boolean.TRUE.toString())) {
|
||||
PropertyUtils.setDefaultInitProperty("feign.httpclient.enabled", "false");
|
||||
PropertyUtils.setDefaultInitProperty("feign.okhttp.enabled", "true");
|
||||
} else {
|
||||
PropertyUtils.setDefaultInitProperty("feign.httpclient.enabled", "true");
|
||||
PropertyUtils.setDefaultInitProperty("feign.okhttp.enabled", "false");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.lingniu.framework.plugin.microservice.common.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.microservice.common.config.MircroServiceConfig;
|
||||
import feign.Client;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.EventListener;
|
||||
import okhttp3.Interceptor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* OkHttpFeignLoadBalancedConfiguration
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(OkHttpClient.class)
|
||||
@ConditionalOnProperty(value = "framework.lingniu.spring.cloud.isOkHttp", havingValue = "true", matchIfMissing = true)
|
||||
class OkHttpFeignLoadBalancedConfiguration {
|
||||
|
||||
@Bean
|
||||
public Client feignClient(okhttp3.OkHttpClient okHttpClient,
|
||||
LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
return new RetryableFeignBlockingLoadBalancerClient(new OkHttpClient(okHttpClient), loadBalancerClient, loadBalancedRetryFactory, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class OkHttpFeignConfiguration {
|
||||
private okhttp3.OkHttpClient okHttpClient;
|
||||
|
||||
@Bean
|
||||
public ConnectionPool httpClientConnectionPool(MircroServiceConfig mircroServiceConfig,
|
||||
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
|
||||
return connectionPoolFactory.create(mircroServiceConfig.getMaxTotal(), mircroServiceConfig.getOkHttp().getTimeToLive(), mircroServiceConfig.getOkHttp().getTimeToLiveUnit());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, Optional<List<Interceptor>> okFeignInterceptors,
|
||||
ConnectionPool connectionPool, MircroServiceConfig mircroServiceConfig) {
|
||||
okhttp3.OkHttpClient.Builder builder = httpClientFactory.createBuilder(mircroServiceConfig.getOkHttp().getDisableSslValidation()).
|
||||
connectTimeout(mircroServiceConfig.getConnectTimeout(), TimeUnit.MILLISECONDS)
|
||||
.readTimeout(mircroServiceConfig.getReadTimeout(), TimeUnit.MILLISECONDS)
|
||||
.writeTimeout(mircroServiceConfig.getOkHttp().getWriteTimeout(), TimeUnit.MILLISECONDS)
|
||||
.followRedirects(mircroServiceConfig.getOkHttp().getIsFollowRedirects())
|
||||
.connectionPool(connectionPool);
|
||||
okFeignInterceptors.ifPresent(n -> {n.forEach(builder::addInterceptor);});
|
||||
this.okHttpClient = builder.build();
|
||||
return okHttpClient;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (okHttpClient != null) {
|
||||
okHttpClient.dispatcher().executorService().shutdown();
|
||||
okHttpClient.connectionPool().evictAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.lingniu.framework.plugin.microservice.common.init.LingniuCloudAutoConfiguration
|
||||
org.springframework.context.ApplicationContextInitializer=\
|
||||
cn.lingniu.framework.plugin.microservice.common.init.LingniuCloudInit
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-microservice-nacos</artifactId>
|
||||
<name>lingniu-framework-plugin-microservice-nacos</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>nacos-client</artifactId>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>nacos-client</artifactId>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-core</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-microservice-common</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
<version>1.3.8</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,72 @@
|
||||
# 框架核心web应用模块
|
||||
|
||||
|
||||
## 概述 (Overview)
|
||||
|
||||
1. 定位: 基于 Alibaba Nacos 封装,支持服务注册、发现、多中心注册等功能的自动化组件
|
||||
2. 核心能力:
|
||||
* 支持多中心注册与服务发现
|
||||
3. 适用场景:
|
||||
* 公司内部微服务统一注册中心
|
||||
* 与 FeignClient 配合实现服务间调用
|
||||
|
||||
|
||||
## 如何配置
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
application:
|
||||
name: lingniu-framework-demo
|
||||
profiles:
|
||||
active: dev
|
||||
cloud:
|
||||
nacos:
|
||||
enabled: true #开启微服务自定义注册,自动获取当前框架信息,启动时间等到metadata中
|
||||
discovery:
|
||||
server-addr: http://nacos-uat-new-inter.xx.net.cn:8848 #注册中心地址
|
||||
username: nacos_test #注册中心用户名
|
||||
password: nacos_test #注册中心密码
|
||||
|
||||
```
|
||||
|
||||
|
||||
## 接口提供端
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public class GithubApiController {
|
||||
|
||||
@GetMapping("/repos/{owner}/{repo}/contributors")
|
||||
public CommonResult<List<Contributor>> contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo) {
|
||||
// 模拟返回贡献者列表
|
||||
Contributor contributor1 = new Contributor("user1", 10);
|
||||
Contributor contributor2 = new Contributor("user2", 5);
|
||||
List<Contributor> response = new ArrayList<>();
|
||||
response.add(contributor1);
|
||||
response.add(contributor2);
|
||||
return CommonResult.success(response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 接口调用方
|
||||
|
||||
```java
|
||||
// name = 接口提供方的应用名
|
||||
@FeignClient(name = "lingniu-framework-provider-demo"
|
||||
//外部接口配置url: @FeignClient(name = "github-api", url = "https://api.github.com"
|
||||
// 方式1 配置fallback
|
||||
// , fallback = GithubApiClientFallBack.class
|
||||
// 方式2 配置fallbackFactory
|
||||
// , fallbackFactory = GithubApiClientFallbackFactory.class
|
||||
)
|
||||
public interface DemoFeignClient {
|
||||
|
||||
@GetMapping("/repos/{owner}/{repo}/contributors")
|
||||
CommonResult<List<Contributor>> contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo);
|
||||
|
||||
@GetMapping("/reposNotFound")
|
||||
CommonResult<List<Contributor>> reposNotFound(@RequestParam("owner") String owner, @RequestParam("repo") String repo);
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user