一、背景
微服务架构中服务间相互调用,客户端会接收到的是一个由服务端返回的Response。使用FeignClient 调用时,例:A服务调用B服务,B服务处理业务时产生了异常,A服务无法获取到的该异常,只能获取到 FeignException 异常。
我的业务场景是,有一个下发功能会调用一个http的外部接口, 前端先调用E服务,在E服务中调用B服务(系统所有的外部接口都在B服务),B服务调用外部接口 产生了一系列异常,E服务需要接收 B服务调用外部接口时产生的异常。
二、业务代码
1、被调用方 B服务
B服务的业务代码中抛出了自定义异常 DjqException:
private JSONObject http(HttpRequestBase http, Header[] headers, String url) {
CloseableHttpClient httpClient = HttpClients.createDefault();
http.setHeaders(headers);
try {
CloseableHttpResponse response = httpClient.execute(http);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
String entity = EntityUtils.toString(response.getEntity());
log.info("大家签:" + url + " {}", entity);
JSONObject jsonObject = JSON.parseObject(entity);
if (HttpStatus.OK.value() == statusCode) {
return jsonObject;
}
// TODO liuyong,接口调用失败,这里可能的原因是clientId或secret不正确
throw new DjqException(jsonObject.getString("message"));
} catch (IOException e) {
e.printStackTrace();
// TODO liuyong,接口调用失败,这里可能的原因是网络问题
throw new DjqException("调用大家签接口失败");
}
}
DjqException 自定义异常类:
package cn.com.ebidding.base.djq.exception;
import lombok.Data;
/**
* @Description: 适用于大家签接口抛出异常
* @Date: 2024/2/29
*/
@Data
public class DjqException extends RuntimeException {
/**
* 错误码
*/
private int code;
/**
* 错误信息
*/
private String msg;
public DjqException(String message) {
this.msg = message;
}
@Deprecated
public DjqException(String code, String message) {
super(message);
this.code = Integer.valueOf(code);
}
public DjqException(int code, String message) {
super(message);
this.code = code;
}
}
自定义异常的转换类:
把异常类抛出的信息放到response中,response给一个指定的状态 518
package cn.com.ebidding.base.djq.exception;
import cn.com.ebidding.common.enums.DjqErrorHttpStatus;
import cn.com.ebidding.common.tool.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
/**
* @Description: 大家签接口产生的异常处理
* @Date: 2024/2/29
*/
@RestControllerAdvice
@Slf4j
public class DjqExceptionHandler {
/**
* @Description: 捕获大家签接口产生的异常
* @Date: 10:07 2024/2/29
* @Param: DjqException
*/
@ExceptionHandler(value = DjqException.class)
public void djqExceptionHandler (HttpServletResponse response, DjqException ex) {
response.setStatus(DjqErrorHttpStatus.JDQ_ERROR.value());
response.addHeader("code", String.valueOf(ex.getCode()));
String msg = "";
try {
msg = java.net.URLEncoder.encode(ex.getMsg(), "utf-8");
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage());
}
response.addHeader("msg", msg);
}
}
/**
* @Description:
* @Date: 2024/2/29
*/
public enum DjqErrorHttpStatus {
JDQ_ERROR(518, "DJQ Business error");
private final int value;
private final String reasonPhrase;
DjqErrorHttpStatus(int value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
public int value() {
return this.value;
}
}
2、调用方 E服务
实现了feignClient的 ErrorDecoder 接口
捕获调用返回的response,通过返回的状态值判断是不是 前面我们封装的异常信息
正常抛出异常 DjqException、Exception就可以了
我是抛给了全局的自定义异常 EbsException ,系统会自动把全局异常封装给接口返回前端
/**
* @Description: 捕获feign调用 大家签异常
* @Date: 2024/2/29
*/
@Configuration
public class FeignErrorDecoder implements ErrorDecoder {
@Override
public EbsException decode(String methodKey, Response response) {
int status = response.status();
// 判断是否是接口抛出的状态值 518
if (DjqErrorHttpStatus.JDQ_ERROR.value() == status) {
// 这里直接拿到我们抛出的异常信息
Map<String, Collection<String>> headers = response.headers();
String message = headers.get("msg").iterator().next();
try {
String decode = URLDecoder.decode(message, "utf-8");
return new EbsException(decode);
} catch (UnsupportedEncodingException e) {
return new EbsException("系统异常,请联系系统开发人员进行处理! ");
}
}
return new EbsException("系统异常,请联系系统开发人员进行处理! ");
}
}