新增feign-starter模块封装

This commit is contained in:
tianliyong 2022-11-03 18:17:54 +08:00
parent 7af650deb5
commit c144f66df5
19 changed files with 453 additions and 172 deletions

View File

@ -0,0 +1,65 @@
<?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-common-clients</artifactId>
<groupId>cn.axzo.framework.client</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>feign-starter</artifactId>
<name>Axzo Common Client Feign Starter</name>
<dependencies>
<!--Compile-->
<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>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hystrix</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
</dependency>
<!--Optional-->
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,64 @@
package cn.axzo.framework.client.feign;
import cn.axzo.framework.domain.web.ApiException;
import cn.axzo.framework.domain.web.code.IRespCode;
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 lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
/**
* @author liyong.tian
* @since 2022/11/3
*/
@RequiredArgsConstructor
@Getter
@Slf4j
public class FeignFallback {
@Nullable
private final Throwable cause;
private final IRespCode respCode;
public <T> ApiResult<T> resp() {
if (cause != null) {
log.error("Enter feign fallback", cause);
if (cause instanceof ApiException) {
return ApiResult.err(((ApiException) cause).getCode(), cause.getMessage());
}
return ApiResult.err(respCode, cause.getMessage());
}
log.error("Enter feign fallbackno cause catch");
return ApiResult.err(respCode);
}
public <T> ApiPageResult<T> pageResp() {
if (cause != null) {
log.error("Enter feign fallback", cause);
if (cause instanceof ApiException) {
return ApiPageResult.err(((ApiException) cause).getCode(), cause.getMessage());
}
return ApiPageResult.err(respCode, cause.getMessage());
}
log.error("Enter feign fallbackno cause catch");
return ApiPageResult.err(respCode);
}
public <T> ApiListResult<T> listResp() {
if (cause != null) {
log.error("Enter feign fallback", cause);
if (cause instanceof ApiException) {
return ApiListResult.err(((ApiException) cause).getCode(), cause.getMessage());
}
return ApiListResult.err(respCode, cause.getMessage());
}
log.error("Enter feign fallbackno cause catch");
return ApiListResult.err(respCode);
}
}

View File

@ -0,0 +1,64 @@
package cn.axzo.framework.client.feign.decoder;
import cn.axzo.framework.core.util.ClassUtil;
import cn.axzo.framework.domain.ServiceException;
import cn.axzo.framework.domain.web.ApiException;
import cn.axzo.framework.domain.web.ClientException;
import cn.axzo.framework.domain.web.code.IRespCode;
import cn.axzo.framework.domain.web.code.RespCode;
import cn.axzo.framework.domain.web.http.HttpStatus;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.framework.jackson.utility.JSON;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import static cn.axzo.framework.core.Constants.CLIENT_MARKER;
import static cn.axzo.framework.domain.web.code.BaseCode.NOT_FOUND;
import static java.lang.String.format;
/**
* @author liyong.tian
* @since 2022/11/3
*/
@Slf4j
public class ApiResultErrorDecoder implements ErrorDecoder {
private static final boolean isHystrixPresent;
static {
isHystrixPresent = ClassUtil.isPresent(
"com.netflix.hystrix.exception.HystrixBadRequestException",
ApiResultErrorDecoder.class.getClassLoader()
);
}
public ApiResultErrorDecoder() {
log.info(format("Add Feign ErrorDecoder: %s, isHystrixPresent: %s", "ApiResultErrorDecoder", isHystrixPresent));
}
@Override
public Exception decode(String methodKey, Response response) {
try {
HttpStatus status = HttpStatus.valueOf(response.status());
if (status == HttpStatus.NOT_FOUND) {
return new ApiException("url[" + response.request().url() + "] not found", NOT_FOUND);
}
ApiResult<?> apiResult = JSON.parseObject(response.body().asInputStream(), ApiResult.class);
String code = apiResult.getCode();
if (code == null) {
return new ServiceException("ApiResult code is null");
}
IRespCode respCode = new RespCode(code, apiResult.getMsg());
if (isHystrixPresent && status.is4xxClientError()) {
return new ClientException(respCode); // 不熔断
}
return new ApiException(respCode);
} catch (Exception e) {
log.error(CLIENT_MARKER, format("%s reading %s ", e.getMessage(), methodKey), e);
return e;
}
}
}

View File

@ -0,0 +1,29 @@
package cn.axzo.framework.client.feign.logger;
import feign.Client;
import feign.Request;
import feign.Response;
import java.io.IOException;
/**
* @author liyong.tian
* @since 2022/11/3
*/
public class ClientDecorator implements Client {
private final Client delegate;
public ClientDecorator(Client delegate) {
this.delegate = delegate;
}
@Override
public Response execute(Request request, Request.Options options) throws RequestIOException {
try {
return delegate.execute(request, options);
} catch (IOException e) {
throw new RequestIOException(request, e);
}
}
}

View File

@ -0,0 +1,179 @@
package cn.axzo.framework.client.feign.logger;
import com.google.common.base.Joiner;
import cn.axzo.framework.domain.http.*;
import cn.axzo.framework.domain.web.http.HttpStatus;
import feign.Request;
import feign.Response;
import feign.Util;
import feign.slf4j.Slf4jLogger;
import lombok.val;
import org.javatuples.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import static cn.axzo.framework.core.Constants.CLIENT_MARKER;
import static java.nio.charset.Charset.defaultCharset;
import static java.util.stream.Collectors.toList;
import static jodd.util.StringPool.*;
/**
* @author liyong.tian
* @since 2022/11/2
*/
@SuppressWarnings("WeakerAccess")
public class FeignLogger extends Slf4jLogger {
private final Logger log;
private final HttpLogFormatter formatter;
public FeignLogger(Class<?> clazz) {
this(clazz, JsonHttpLogFormatter.INSTANCE);
}
public FeignLogger(Class<?> clazz, HttpLogFormatter formatter) {
super(clazz);
this.log = LoggerFactory.getLogger(clazz);
if (formatter == null) {
this.formatter = JsonHttpLogFormatter.INSTANCE;
} else {
this.formatter = formatter;
}
}
@Override
protected void log(String configKey, String format, Object... args) {
IllegalStateException exception = new IllegalStateException("this method may not be invoked");
log.error("will not happen", exception);
throw exception;
}
@Override
protected void logRequest(String configKey, Level logLevel, Request request) {
// do nothing
}
@Override
protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
if (logLevel == Level.NONE || !(ioe instanceof RequestIOException)) {
//响应日志
HttpResponseLog responseLog = _responseLog(configKey, ioe, elapsedTime);
//打印响应日志
log.error(CLIENT_MARKER, formatter.format(responseLog), ioe);
return ioe;
}
Request request = ((RequestIOException) ioe).getRequest();
//请求日志
HttpRequestLog requestLog = _requestLog(request);
//响应日志
HttpResponseLog responseLog = _responseLog(request.url(), ioe, elapsedTime);
//打印完整的请求响应日志
log.error(CLIENT_MARKER, formatter.format(requestLog, responseLog), ioe);
return ioe;
}
@Override
protected void logRetry(String configKey, Level logLevel) {
log.info(CLIENT_MARKER, methodTag(configKey) + "---> RETRYING");
}
@Override
protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime)
throws IOException {
if (logLevel == Level.NONE) {
return response;
}
//请求日志
val requestLog = _requestLog(response.request());
//响应日志
val responsePair = _responseLog(response, elapsedTime);
//打印完整的请求响应日志
log.info(CLIENT_MARKER, formatter.format(requestLog, responsePair.getValue0()));
return responsePair.getValue1();
}
private HttpRequestLog _requestLog(Request request) {
Objects.requireNonNull(request, "request cannot be null");
val logBuilder = HttpRequestLog.builder();
//protocol
logBuilder.protocol("HTTP/1.1");
//method
logBuilder.method(request.method());
//url
logBuilder.url(request.url());
//headers
List<String> requestHeaders = request.headers().entrySet().stream()
.map(entry -> entry.getKey() + COLON + SPACE + Joiner.on(COMMA + SPACE).join(entry.getValue()))
.collect(toList());
logBuilder.headers(requestHeaders);
//body
if (!HttpHeaderUtil.isMultipartRequest(requestHeaders)) {
logBuilder.body(request.body() == null ? null : new String(request.body()));
}
return logBuilder.build();
}
private Pair<HttpResponseLog, Response> _responseLog(Response response, long elapsedTime) throws IOException {
val logBuilder = HttpResponseLog.builder();
HttpStatus status = HttpStatus.valueOf(response.status());
List<String> responseHeaders = response.headers().entrySet().stream()
.map(entry -> entry.getKey() + COLON + SPACE + Joiner.on(COMMA + SPACE).join(entry.getValue()))
.collect(toList());
//url
logBuilder.url(response.request().url());
//status
logBuilder.status(status.value());
//msg
logBuilder.msg(status.getReasonPhrase());
//headers
logBuilder.headers(responseHeaders);
//tookMs
logBuilder.tookMs(elapsedTime);
//body
boolean canReadBody = response.body() != null && !HttpHeaderUtil.isDownloadResponse(responseHeaders);
if (canReadBody) {
byte[] bodyBytes = Util.toByteArray(response.body().asInputStream());
logBuilder.body(new String(bodyBytes, defaultCharset()));
//流只能读一次需要把响应重新构造一次
response = response.toBuilder().body(bodyBytes).build();
} else {
logBuilder.body(EMPTY);
}
return Pair.with(logBuilder.build(), response);
}
private HttpResponseLog _responseLog(String url, IOException ioe, long elapsedTime) {
return HttpResponseLog.builder()
.url(url)
.tookMs(elapsedTime)
.errorMsg(ioe.getClass().getSimpleName() + ": " + ioe.getMessage())
.build();
}
}

View File

@ -0,0 +1,24 @@
package cn.axzo.framework.client.feign.logger;
import feign.Request;
import lombok.Getter;
import java.io.IOException;
/**
* @author liyong.tian
* @since 2022/11/2
*/
public class RequestIOException extends IOException {
@Getter
private final Request request;
private final IOException exception;
public RequestIOException(Request request, IOException cause) {
super(cause instanceof RequestIOException ? ((RequestIOException) cause).exception : cause);
this.request = request;
this.exception = cause;
}
}

View File

@ -17,5 +17,6 @@
<modules>
<module>retrofit-starter</module>
<module>feign-starter</module>
</modules>
</project>

View File

@ -43,11 +43,11 @@ public class PageResp<T> {
return new PageResp<T>(pageNum, pageSize, 0L, new ArrayList<>());
}
public static <T> PageResp<T> list(Long page, Long pageSize, Long totalCount, List<T> data) {
public static <T> PageResp<T> list(Long pageNum, Long pageSize, Long totalCount, List<T> data) {
if (CollectionUtils.isEmpty(data)) {
return zero(page, pageSize);
return zero(pageNum, pageSize);
}
return new PageResp<T>(page, pageSize, totalCount, data);
return new PageResp<T>(pageNum, pageSize, totalCount, data);
}
public static <T> PageResp<T> list(IPage<?> page, List<T> data) {
@ -57,4 +57,11 @@ public class PageResp<T> {
return new PageResp<T>(page.getCurrent(), page.getSize(), page.getTotal(), data);
}
public static <T> PageResp<T> list(PageQO page, Long totalCount, List<T> data) {
if (CollectionUtils.isEmpty(data)) {
return zero(page.getPage(), page.getPageSize());
}
return new PageResp<T>(page.getPage(), page.getPageSize(), totalCount, data);
}
}

View File

@ -139,4 +139,8 @@ public class ApiPageResult<E> extends ApiCoreResult<PageData<E>>{
public Page<E> toPage(Pageable pageable) {
return new PageImpl<>(data == null ? Lists.newArrayList() : data.getList(), pageable, data.getTotalCount());
}
public PageResp<E> toPage() {
return new PageResp<E>(data.getPageNum().longValue(), data.getPageSize().longValue(), data.getTotalCount(), data.getList() == null ? Lists.newArrayList() : data.getList());
}
}

View File

@ -1,161 +0,0 @@
package cn.axzo.framework.domain.web.result;
import static cn.axzo.framework.domain.web.code.BaseCode.SUCCESS;
import static com.google.common.collect.Lists.newArrayList;
import cn.axzo.framework.domain.page.Page;
import cn.axzo.framework.domain.page.PageImpl;
import cn.axzo.framework.domain.page.PageResp;
import cn.axzo.framework.domain.page.PageVerbose;
import cn.axzo.framework.domain.page.Pageable;
import cn.axzo.framework.domain.web.code.IRespCode;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import io.swagger.annotations.ApiModelProperty;
import java.beans.ConstructorProperties;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @Description
* @Author liyong.tian
* @Date 2020/9/7 20:32
**/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@JsonPropertyOrder({"total", "code", "msg", "data", "pageNum", "pageSize", "verbose"})
public class ApiPageResult1<E> extends ApiCoreResult<List<E>> {
@ApiModelProperty(value = "总记录数", position = 101, required = true)
private final Long total;
@ApiModelProperty(value = "当前页", position = 102)
private final Integer pageNum;
@ApiModelProperty(value = "每页显示数量", position = 103)
private final Integer pageSize;
@ApiModelProperty(value = "分页冗余信息", position = 104)
private final PageVerbose verbose;
public static <E> ApiPageResult1<E> empty() {
return ok(newArrayList(), 0L);
}
public static <E> ApiPageResult1<E> ok(Page<E> page) {
Pageable current = page.current();
// data
List<E> data = page.getContent();
if (!current.needContent() && data.isEmpty()) {
data = null;
}
// pageNum & pageSize
Integer pageNum = null;
Integer pageSize = null;
if (current.isFixEdge() && current.needContent()) {
pageNum = current.getPageNumber();
pageSize = current.getPageSize();
}
// verbose
PageVerbose verbose = PageVerbose.of(page).orElse(null);
return ok(data, page.getTotal(), pageNum, pageSize, verbose);
}
public static <E> ApiPageResult1 ok(PageInfo<E> pageInfo) {
return ok(pageInfo.getList(), pageInfo.getTotal(), pageInfo.getPageNum(), pageInfo.getPageSize());
}
public static <E> ApiPageResult1<E> ok(org.springframework.data.domain.Page<E> page) {
return ok(page.getContent(), page.getTotalElements());
}
public static <E> ApiPageResult1<E> ok(IPage<E> page) {
return ok(page.getRecords(), page.getTotal(), (int)page.getCurrent(), (int)page.getSize());
}
public static <E> ApiPageResult1<E> ok(PageResp<E> page) {
return ok(page.getList(), page.getTotalCount(), page.getPage().intValue(), page.getPageSize().intValue());
}
public static <E> ApiPageResult1<E> ok(List<E> data, Long total) {
return build(total, SUCCESS.getRespCode(), SUCCESS.getMessage(), data,
null, null, null);
}
public static <E> ApiPageResult1<E> ok(List<E> data, Long total, Integer pageNumber, Integer pageSize) {
return build(total, SUCCESS.getRespCode(), SUCCESS.getMessage(), data, pageNumber, pageSize, null);
}
public static <E> ApiPageResult1<E> ok(List<E> data, Long total, Integer pageNumber, Integer pageSize,
PageVerbose verbose) {
return build(total, SUCCESS.getRespCode(), SUCCESS.getMessage(), data, pageNumber, pageSize, verbose);
}
public static <E> ApiPageResult1<E> err(IRespCode code) {
return build(code);
}
public static <E> ApiPageResult1<E> err(IRespCode code, String message) {
return err(code.getRespCode(), message);
}
public static <E> ApiPageResult1<E> err(String code, String message) {
return build(null, code, message, null, null, null, null);
}
public static <E> ApiPageResult1<E> build(IRespCode code) {
return build(null, code, null, null, null);
}
public static <E> ApiPageResult1<E> build(Long total, IRespCode code, List<E> data,
Integer pageNum, Integer pageSize) {
return build(total, code.getRespCode(), code.getMessage(), data, pageNum, pageSize, null);
}
public static <E> ApiPageResult1<E> build(Long total, String code, String message, List<E> data,
Integer pageNum, Integer pageSize) {
return new ApiPageResult1<>(total, code, message, data, pageNum, pageSize, null);
}
public static <E> ApiPageResult1<E> build(Long total, String code, String message, List<E> data,
Integer pageNum, Integer pageSize, PageVerbose verbose) {
return new ApiPageResult1<>(total, code, message, data, pageNum, pageSize, verbose);
}
@ConstructorProperties({"total", "code", "message", "data", "pageNum", "pageSize", "verbose"})
public ApiPageResult1(Long total, String code, String message, List<E> data, Integer pageNum, Integer pageSize,
PageVerbose verbose) {
super(code, message, data);
this.total = total;
this.pageNum = pageNum;
this.pageSize = pageSize;
this.verbose = verbose;
}
@Override
public Map<String, Object> toMap() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("code", code);
map.put("msg", msg);
map.put("total", total);
map.put("data", data);
map.put("pageNum", pageNum);
map.put("pageSize", pageSize);
map.put("verbose", verbose);
return map;
}
public Page<E> toPage(Pageable pageable) {
return new PageImpl<>(data == null ? Lists.newArrayList() : data, pageable, total);
}
}

View File

@ -20,7 +20,7 @@ import static jodd.util.StringPool.COMMA;
* 1/100
* [1,100]
*
* @author jearton
* @author liyong.tian
* @since 2017/3/3
*/
public class BigFractionDeserializer extends JsonDeserializer<BigFraction> {

View File

@ -20,7 +20,7 @@ import static jodd.util.StringPool.COMMA;
* 1/100
* [1,100]
*
* @author jearton
* @author liyong.tian
* @since 2017/3/2
*/
public class FractionDeserializer extends JsonDeserializer<Fraction> {

View File

@ -14,7 +14,7 @@ import java.io.IOException;
* 1/100
* [1,100]
*
* @author jearton
* @author liyong.tian
* @since 2017/3/2
*/
public class BigFractionSerializer extends JsonSerializer<BigFraction> {

View File

@ -14,7 +14,7 @@ import java.io.IOException;
* 1/100
* [1,100]
*
* @author jearton
* @author liyong.tian
* @since 2017/3/2
*/
public class FractionSerializer extends JsonSerializer<Fraction> {

View File

@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
import static cn.axzo.framework.jackson.datatype.string.PackageVersion.VERSION;
/**
* @author jearton
* @author liyong.tian
* @since 2017/1/6
*/
public class TrimModule extends SimpleModule {

View File

@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
import java.io.IOException;
/**
* @author jearton
* @author liyong.tian
* @since 2017/1/6
*/
public class TrimDeserializer extends StdScalarDeserializer<String> {

View File

@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
/**
* @author jearton
* @author liyong.tian
* @since 2017/1/6
*/
public class TrimDeserializerModifier extends BeanDeserializerModifier {

View File

@ -12,7 +12,7 @@ import java.io.IOException;
import java.lang.reflect.Type;
/**
* @author jearton
* @author liyong.tian
* @since 2017/1/6
*/
public class TrimSerializer extends StdScalarSerializer<String> {

View File

@ -123,6 +123,11 @@
<artifactId>retrofit-starter</artifactId>
<version>${axzo-commons.version}</version>
</dependency>
<dependency>
<groupId>cn.axzo.framework.client</groupId>
<artifactId>feign-starter</artifactId>
<version>${axzo-commons.version}</version>
</dependency>
<!--common jackson-->
<dependency>