SpringCloud请求异常处理封装BusinessException自定义异常类

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud请求异常处理封装BusinessException自定义异常类相关的知识,希望对你有一定的参考价值。

文章目录

请求异常处理

1 异常信息格式

系统在交互中难免会有异常发生,前端为了解析异常信息向用户提示特定义了异常信息的返回格式,如下:

1、返回response状态说明

状态码说明
200成功
401没有权限
500程序错误(需要自定义错误体)

2、自定义错误体


    "errCode": "你定义的错误码", 
    "errMessage": "错误说明" 

例如下面根据不同的业务自定义的错误码:

2 异常处理流程

截至目前系统并没有按照前端要求返回异常信息,测试如下:

注册商户时输入一个错误的验证码,返回信息如下

 
    "timestamp": "2021‐12‐10T10:06:19.936+0000", 
    "status": 500, 
    "error": "Internal Server Error", 
    "message": "验证码错误", 
    "path": "/merchant/merchants/register" 

从上边的返回信息得知,状态码为500符合要求,按前端的规范定义的错误信息要写在“errMessage” 中,显然不符合要求。

系统规范了异常处理流程,如下:

1、在服务层抛出自定义异常类型及不可预知异常类型。

上图中BusinessException为系统的自定义异常类型,程序中在代码显示抛出该异常,此类异常是程序员可预知的。

另一部分是系统无法预知的异常,如:数据库无法连接,服务器宕机等场景下所抛出的异常,此类异常是程序员无法预知的异常。

2、应用层接收到服务层抛出异常继续向上抛出,应用层自己也可以抛出自定义异常类型及不可预知异常类型。

3、统一异常处理器捕获到异常进行解析。

判断如果为自定义异常则直接取出错误代码及错误信息,因为程序员在抛出自定义异常时已将错误代码和异常信息指定。

如果为不可预知的异常则统一定义为99999异常代码。

4、统一异常处理器将异常信息格式为前端要求的格式响应给前端。

服务端统一将异常信息封装在下边的Json格式中返回:


    "errCode": "000000", 
    "errMessage": "错误说明" 
 

3 自定义业务异常类

1、在shanjupay-common工程的com.shanjupay.common.domain包下添加业务异常类BusinessException:

package com.shanjupay.common.domain;

/**
 * 自定义的异常类型
 * @author Administrator
 * @version 1.0
 **/
public class BusinessException extends RuntimeException 

    private ErrorCode errorCode;

    public BusinessException(ErrorCode errorCode) 
        super();
        this.errorCode = errorCode;
    
    public BusinessException() 
        super();
    

    public void setErrorCode(ErrorCode errorCode) 
        this.errorCode = errorCode;
    

    public ErrorCode getErrorCode() 
        return errorCode;
    

2、定义错误代码

在common工程专门定义了ErrorCode接口及CommonErrorCode通用代码。

编写 ErrorCode 接口

package com.shanjupay.common.domain;

public interface ErrorCode 

    int getCode();

    String getDesc();


编写 CommonErrorCode通用代码

package com.shanjupay.common.domain;


/**
 * 异常编码
 */
public enum CommonErrorCode implements ErrorCode 

	公用异常编码 //
	E_100101(100101,"传入参数与接口不匹配"),
	E_100102(100102,"验证码错误"),
	E_100103(100103,"验证码为空"),
	E_100104(100104,"查询结果为空"),
	E_100105(100105,"ID格式不正确或超出Long存储范围"),
	E_100106(100106,"上传错误"),
	E_100107(100107,"发送验证码错误"),
	E_100108(100108,"传入对象为空"),
	E_100109(100109,"手机号格式不正确"),
	E_100110(100110,"用户名为空"),
	E_100111(100111,"密码为空"),
	E_100112(100112,"手机号为空"),
	E_100113(100113,"手机号已存在"),
	E_100114(100114,"用户名已存在"),
	E_100115(100115,"密码不正确"),

	SAAS服务异常编码110 //
	E_110001(110001,"账号不存在"),
	E_110002(110002,"角色编码在同一租户中已存在,不可重复"),
	E_110003(110003,"角色为空"),
	E_110004(110004,"角色已绑定账号,被使用中不可删除"),
	E_110005(110005,"权限集合为空"),
	E_110006(110006,"参数为空"),
	E_110007(110007,"未查询到租户关联的角色"),
	E_110008(110008,"账号被其他租户使用,不可删除"),

	商户服务异常编码200//
	E_200001(200001,"企业名称不能为空"),
	E_200002(200002,"商户不存在"),
	E_200003(200003,"商户还未通过认证审核,不能创建应用"),
	E_200004(200004,"应用名称已经存在,请使用其他名称"),
	E_200005(200005,"应用不属于当前商户"),
	E_200006(200006,"门店不属于当前商户"),
	E_200007(200007,"二维码生成失败"),
	E_200008(200008,"授权码为空"),
	E_200009(200009,"订单标题为空"),
	E_200010(200010,"订单金额为空"),
	E_200011(200011,"授权码格式有误"),
	E_200012(200012,"租户不存在"),
	E_200013(200013,"员工不存在"),
	E_200014(200014,"商户下未设置根门店"),
	E_200015(200015,"未查询到该门店"),
	E_200016(200016,"资质申请已通过,无需重复申请"),
	E_200017(200017,"商户在当前租户下已经注册,不可重复注册"),
	E_200018(200018,"商户下的根门店,不可删除"),

	交易服务异常编码300//
	E_300001(300001,"支付金额为空"),
	E_300002(300002,"openId为空"),
	E_300003(300003,"appId为空"),
	E_300004(300004,"商户id为空"),
	E_300005(300005,"服务类型编码为空"),
	E_300006(300006,"订单金额转换异常"),
	E_300007(300007,"原始支付渠道为空"),
	E_300008(300008,"已存在相同的支付参数,不可重复配置"),
	E_300009(300009,"传入对象为空或者缺少必要的参数"),
	E_300010(300010,"应用没有绑定服务类型,不允许配置参数"),

	E_300110(300110,"交易单号不能为空"),


	支付渠道代理服务异常编码400//
	E_400001(400001,"微信确认支付失败"),
	E_400002(400002,"支付宝确认支付失败"),

	运营服务异常编码500//

	特殊异常编码/
	E_999991(999991,"调用微服务-授权服务 被熔断"),
	E_999992(999992,"调用微服务-用户服务 被熔断"),
	E_999993(999993,"调用微服务-资源服务 被熔断"),
	E_999994(999994,"调用微服务-同步服务 被熔断"),

	E_999910(999910,"调用微服务-没有传tenantId租户Id"),
	E_999911(999911,"调用微服务-没有json-token令牌"),
	E_999912(999912,"调用微服务-json-token令牌解析有误"),
	E_999913(999913,"调用微服务-json-token令牌有误-没有当前租户信息"),
	E_999914(999914,"调用微服务-json-token令牌有误-该租户下没有权限信息"),

	E_NO_AUTHORITY(999997,"没有访问权限"),
	CUSTOM(999998,"自定义异常"),
	/**
	 * 未知错误
	 */
	UNKNOWN(999999,"未知错误");


	private int code;
	private String desc;

	public int getCode() 
		return code;
	

	public String getDesc() 
		return desc;
	

	private CommonErrorCode(int code, String desc) 
		this.code = code;
		this.desc = desc;
	


	public static CommonErrorCode setErrorCode(int code) 
		for (CommonErrorCode errorCode : CommonErrorCode.values()) 
			if (errorCode.getCode()==code) 
				return errorCode;
			
		
		return null;
	


目录结构如下:

4 自定义业务异常处理器

1、在shanjupay‐common工程的com.shanjupay.common.domain包下添加错误响应包装类RestErrorResponse:

package com.shanjupay.common.domain;

import io.swagger.annotations.ApiModel;
import lombok.Data;

/**
 * @author Administrator
 * @version 1.0
 **/
@ApiModel(value = "RestErrorResponse", description = "错误响应参数包装")
@Data
public class RestErrorResponse 

    private String errCode;

    private String errMessage;

    public RestErrorResponse(String errCode,String errMessage)
        this.errCode = errCode;
        this.errMessage= errMessage;
    

2、定义全局异常处理器

全局异常处理器使用ControllerAdvice注解实现,ControllerAdvice是SpringMVC3.2提供的注解,用 ControllerAdvice可以方便实现对Controller面向切面编程,具体用法如下:

注解说明
ControllerAdvice和ExceptionHandler注解实现全局异常处理
ControllerAdvice和ModelAttribute注解实现全局数据绑定
ControllerAdvice生InitBinder注解实现全局数据预处理

这里用到的是第一种用法:ControllerAdvice和ExceptionHandler结合可以捕获Controller抛出的异常

ControllerAdvice和ExceptionHandler结合可以捕获Controller抛出的异常,根据异常处理流程,Service和持久层最终都会抛给Controller,所以此方案可以实现全局异常捕获,异常被捕获到即可格式为前端要的信息格式响应给前端。

在shanjupay‐merchant‐application工程 com.shanjupay.merchant.common.intercept添加

GlobalExceptionHandler:

package com.shanjupay.merchant.common.intercept;

import com.shanjupay.common.domain.BusinessException;
import com.shanjupay.common.domain.CommonErrorCode;
import com.shanjupay.common.domain.ErrorCode;
import com.shanjupay.common.domain.RestErrorResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 全局异常处理器
 * @author Administrator
 * @version 1.0
 **/
@ControllerAdvice//与@Exceptionhandler配合使用实现全局异常处理:可以捕获Controller抛出的异常
public class GlobalExceptionHandler 
    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    //捕获Exception异常
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public RestErrorResponse processExcetion( HttpServletRequest request,
                                               HttpServletResponse response,
                                              Exception e)
        //解析异常信息
        //如果是系统自定义异常,直接取出errCode和errMessage
        if(e instanceof BusinessException)
            LOGGER.info(e.getMessage(),e);
            //解析系统自定义异常信息
            BusinessException businessException= (BusinessException) e;
            ErrorCode errorCode = businessException.getErrorCode();
            //错误代码
            int code = errorCode.getCode();
            //错误信息
            String desc = errorCode.getDesc();
            return new RestErrorResponse(String.valueOf(code),desc);
        

        LOGGER.error("系统异常:",e);
        //统一定义为99999系统未知错误
        return new RestErrorResponse(String.valueOf(CommonErrorCode.UNKNOWN.getCode()),CommonErrorCode.UNKNOWN.getDesc());

    

5 抛出自定义异常

按照异常处理流程,应用层抛出自定义异常由异常处理器进行解析。

1、校验验证码接口抛出 BusinessException

修改商户平台应用工程中SmsServicer的verifificationMessageCode接口

public void checkVerifyCode(String verifyKey, String verifyCode) throws BusinessException;

接口实现中抛出异常自定义异常类型

/**
 * 校验手机验证码
 *
 * @param verifiyKey  验证码的key
 * @param verifiyCode 验证码
 */
@Override
public void checkVerifiyCode(String verifiyKey, String verifiyCode) throws BusinessException 
    //校验验证码的url
    String url = "http://localhost:56085/sailing/verify?name=sms&verificationCode="+verifiyCode+"&verificationKey="+verifiyKey;
    Map bodyMap = null;
    try 
        //使用restTemplate请求验证码服务
        ResponseEntity<Map> exchange = restTemplate.exchange(url, HttpMethod.POST, HttpEntity.EMPTY, Map.class);
        log.info("请求验证码服务,得到响应:", JSON.toJSONString(exchange));
        bodyMap = exchange.getBody();
    catch (Exception e)
        e.printStackTrace();
        throw new BusinessException(CommonErrorCode.E_100102);
        //  throw new RuntimeException("校验验证码失败");
    
    if(bodyMap == null || bodyMap.get("result") == null || !(Boolean) bodyMap.get("result"))
        throw new BusinessException(CommonErrorCode.E_100102);
    

2、测试

请求商户注册,输出一个错误的验证码,返回信息如下:

 
    "errCode": "100102", 
    "errMessage": "验证码错误" 
 

3、测试不可预知异常

故意在Controller中制造异常,测试是否抛出未知错误异常。

代码如下:

@PostMapping("/merchants/register") 
public MerchantRegisterVO registerMerchant(@RequestBody MerchantRegisterVO merchantRegister) 
    int i=1/0;//故意制造异常 
    .... 

请商户注册,返回信息如下:

 
    "errMessage": "未知错误", 
    "errCode": "999999" 

以上是关于SpringCloud请求异常处理封装BusinessException自定义异常类的主要内容,如果未能解决你的问题,请参考以下文章

SpringCloud请求响应数据转换

SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解

SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解

企业分布式微服务云SpringCloud SpringBoot mybatis Spring Boot中Web应用的统一异常处理

axios对请求各种异常情况处理的封装方法

axios请求封装,请求异常统一处理