java应用中,调用第三方api如何处理异常(系统异常、操作异常等等)
一个应用大多数都要调用其它服务接口获取相关资源,而第三方服务会有很多错误码,有相关系统异常,用户操作异常,授权异常等等。其实对于自己的应用而讲,可以两种:系统异常和用户非法操作异常,对应自己项目中的http状态为: INTERNAL_SERVER_ERROR(500, "Internal Server Error")、 OK(200, "OK")(这里不讨论自身应用的参数校验,spring帮我们做好了)。
一般系统异常是我们开发者比较关注的,因为导致这种异常发生的原因在于代码bug的出现,
比如:自身系统的系统异常;第三方接口返回的契约异常:如
传入的参数不符合第三方接口数据格式要求、当前代理客户没有部分或全部航班、舱位的销售权限、取消选座操作异常等等,这类异常一般让提示用户重试或者联系客服,一般联系客服,是客户重试也无法解决的。
用户非法操作异常 也是比较常见的,比如:订票限制成人至少要有1个,最多只能有9个、组成往返连程的航班必须是同一航空公司的航班、请确认订单中成人旅客证件号码的正确性等等,这些第三方给的用户操作异常信息必须返回给客户,让客户自己处理才能解决。
系统异常,需要web端或移动端判断http的错误码, 用户非法操作异常需要判断返回的json数据中的returnCode,当returnCode 为0,表示业务成功,非0,表示用户非法操作,需要提示对应 returnMsg。
(注:其实系统异常也可以返回http 200,然后用一个特定的returnCode比如99999表示)。
为了处理这些异常,项目主要利用对错误码分类,分别抛出对应的自定义异常(便于分析问题原因),利用spring的ControllerAdvice 和aop对这些异常处理。
特别的,对应第三方接口的参数校验,方法参数使用了注解@Valid,这是spring所支持的,详见官方文档,会抛出ConstraintViolationException异常,为了明确的表明是第三方接口参数校验异常,利用aop,重新包装为自定义异常RemoteServiceMethodArgumentNotValidException抛出。
【 普通方法参数校验,需要对参数加入@Valid注解,如:
public List getCountryAreaCode(@NotNull LanguageEnum languageEnum)、
public List searchFlightsBatch(@Valid SearchFlightsBatchSearchBean searchInfo) ;
SearchFlightsBatchSearchBean 中的属性根据需要加上javax.validation相关包中的注解,属性为对象,要校验必须加上 @Valid注解。】
为了方便处理第三方的异常类型,我们用枚举列出第三方接口约定的异常,我们需要把错误码归类:
/**xxx
* Created by xxxxx on 2017/5/15.
*/
@Slf4j
@Getter
@ToString
public enum SpringairlineErrCode {
ERR665_007("ERR665-007", "请确认订单中成人旅客证件号码的正确性"),
AIRSALESWS_EXCEP_111("AIRSALESWS-EXCEP-111", "当前代理客户没有该订单的操作权限"),
ERR803_166("ERR803-166", "需要绑定保险"),
ERR803_074("ERR803-074", "必填参数未填写完整"),
ERR814_001("ERR814-001", "参数异常"),
ERR808_999("ERR808-999", "其它异常"),
...................................
UNKNOWNERROR("unknown", "unknown error");
private String errCode;
private String errMsg;
SpringairlineErrCode(String errCode, String errMsg) {
this.errCode = errCode;
this.errMsg = errMsg;
}
private static Map codeMap = new HashMap<>();
static {
for (SpringairlineErrCode code : SpringairlineErrCode.values()) {
put(codeMap, code);
}
}
public static SpringairlineErrCode of(final String errCode) {
log.error("春秋错误码:{}", errCode);
SpringairlineErrCode springairlineErrCode = codeMap.get(errCode);
...................................
if (springairlineErrCode == null) {
log.warn("春秋未知错误码:{}", errCode);
springairlineErrCode = SpringairlineErrCode.UNKNOWNERROR;
}
return springairlineErrCode;
}
//用户非法操作异常
public static boolean isUserIllegalOperation(SpringairlineErrCode code) {
// return userIllegalOperationCode.contains(code);
return !sysException.contains(code);
}
//系统异常
private static Set sysException = new HashSet<>();
static {
put(sysException, AIRSALESWS_EXCEP_111);
,,,,,,,
);
}
private static void put(Map map, SpringairlineErrCode code) {
map.put(code.getErrCode(), code);
}
private static void put(Set set, SpringairlineErrCode code) {
set.add(code);
}
}
枚举中用
sysException 表示所有系统异常,其他的就是用户操作异常。
为了对接口的返回值处理我们需要定义一个工具类:
SpringairlineResponseUtil
private static void handle(String errCode) {
SpringairlineErrCode errCode = SpringairlineErrCode.of(errCode);
if (SpringairlineErrCode.isUserIllegalOperation(errCode)) {
throw new UserIllegalOperationException(errCode.getErrCode(), errCode.getErrMsg());
}
throw new RemoteServiceException(errCode.getErrCode(), errCode.getErrMsg());
}
如果接口返回的错误码是用户操作异常,我们抛出自定义异常:UserIllegalOperationException,其他抛出自定义异常RemoteServiceException.
为了对这些异常处理,需用使用spring的RestControllerAdvice处理,
/**
* Created by xxxxxxxx on 2017/6/13.
*
* 异常处理,rest端
*/
@RestControllerAdvice
@Slf4j
public class ExceptionResponseHandlerConfig {
/**
* 用户操作异常提示,展现给客户端
*
* @param ex
* @param request
* @return
*/
@ExceptionHandler(UserIllegalOperationException.class)
public ResponseEntity> handleSysException(UserIllegalOperationException ex, WebRequest request) {
ModelResult
为了更好的分析问题,我们对第三方接口的参数要求也用了spring的@Valid注解,sping会抛出ConstraintViolationException异常,为了方便定位是第三方接口参数校验异常,用aop 方法抛出自定义异常:RemoteServiceMethodArgumentNotValidException,RestControllerAdvice会当做内部服务器异常处理。
@Around(("pointcutSpringairline() "))
public Object aroundSampleCreation(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String argss = null;
Object[] args = proceedingJoinPoint.getArgs();
if (args != null) {
ObjectMapper objectMapper = new ObjectMapper();
argss = objectMapper.writeValueAsString(args);
}
log.info("执行方法:{} 参数:{}", proceedingJoinPoint.getSignature(), argss);
Object proceed = null;
try {
proceed = proceedingJoinPoint.proceed();
} catch (ConstraintViolationException e) {
Set> violations = e.getConstraintViolations();
String message = violations.stream().map(t -> t.getMessage()).collect(joining(","));
throw new RemoteServiceMethodArgumentNotValidException(message + " ============>>>>>> " + violations.toString(), e);
}
return proceed;
}
当spring RestControllerAdvice 对异常做了处理后,系统异常会以INTERNAL_SERVER_ERROR(500, "Internal Server Error")给予客户端提示,用户非法操作异常以OK(200, "OK")返回,当返回http OK(200, "OK")时,客户端还需要判断ModelResult对象中的
/**
* 0 代表成功,其余自定义错误码
*/
private String returnCode = "0";
/**
* 错误信息描述
*/
private String returnMsg = "成功!";
当returnCode为0,代码业务执行成功,非0,需要提示用户的非法操作异常信息为returnMsg内容。
当第三方接口不仅仅用错误码表示异常,用http状态码时,我们只能对http client 相关的客户端框架做处理,其思路是一样的。
博文的主要思路是对第三方接口的返回错误码分类,分别抛出自定义异常,然后利用spring 的RestControllerAdvice 和aop 功能,对自定义异常进行封装返回数据给客户端处理。