用SpringCloud Alibaba搭建属于自己的微服务(十)~基础搭建~自定义异常、统一结果集和全局异常处理器

一.自定义异常体系

1.背景

在业务代码的开发中,往往会有这种情况,代码逻辑走到某一步,通过if的判断发现程序的逻辑无法继续往下面走了,否则会有一定的问题,这时候我们需要抛出异常组织程序往下进行,快速响应这个请求,并且回滚事物,这就是我们的自定义异常体系.

2.设计

如何设计自定义异常体系,需要我们对自身系统的业务有一定的了解,例如一个商城,可能会有以下的自定义异常.

异常 场景
OrderException.class 订单业务发生异常
PayException.class 支付业务发生异常
GoodsException.class 商品业务发生异常

我们这里不做如何详细的异常体系设计,暂时只区分客户端异常,服务端异常.
(1)代码
CcmMallException.java

package com.ccm.common.exception;

/**
 *  @Description 自定义异常超类
 *  @Author zhouzhiwu
 *  @CreateTime 2020/07/10 14:35
 */
public class CcmMallException extends RuntimeException {
}

CustomerException .java

package com.ccm.common.exception;

import lombok.Getter;

/**
 * @Description 客户端异常
 * @Author zhouzhiwu
 * @CreateTime 2020/7/10 14:40
 */
@Getter
public class CustomerException extends CcmMallException {

    private CodeEnum codeEnum;  //状态码
    private String errorMessage;  //错误详细信息

    public CustomerException(String errorMessage) {
       this.codeEnum = CodeEnum.ILLEGAL_REQUEST;
       this.errorMessage = errorMessage;
    }

    public CustomerException(CodeEnum codeEnum, String errorMessage) {
        this.codeEnum = codeEnum;
        this.errorMessage = errorMessage;
    }
}

ServerException.java

package com.ccm.common.exception;


import lombok.Getter;

/**
 *  @Description 服务端异常
 *  @Author zhouzhiwu
 *  @CreateTime 2020/07/10 14:43
 */
@Getter
public class ServerException extends CcmMallException {
    private CodeEnum codeEnum;  //状态码
    private String errorMessage;  //错误详细信息

    public ServerException(String errorMessage) {
        this.codeEnum = CodeEnum.SYSTEM_INNER_ERROR;
        this.errorMessage = errorMessage;
    }

    public ServerException(CodeEnum codeEnum, String errorMessage) {
        this.codeEnum = codeEnum;
        this.errorMessage = errorMessage;
    }
}

二.统一结果集

1.common包里加入Reultset类,提供构造统一结果集的静态方法

package com.ccm.common.exception.result;

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;

/**
 * @Description 统一结果集
 * @Author zhouzhiwu
 * @CreateTime 2020/7/14 17:36
 */
@Slf4j
@Getter
@ToString
public class ResultSet<T> {
    private Integer code; //状态码

    private String codeMessage; //状态码信息

    @Setter
    private String errorMessage;   //前后端对接错误信息

    @Setter
    private String errorMessageToUser; //给用户看的错误信息

    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss:SSS",timezone = "GMT+8")   //jackson指定时间转换格式
    @JSONField(format="yyyy-MM-dd HH:mm:ss:SSS")    //fastjson指定时间转换格式
    private Date timestamp = new Date();

    @Setter
    private T data; //响应数据

    //私有构造器
    private ResultSet() {}

    //响应成功,无响应数据
    public static ResultSet success() {
        ResultSet resultSet = new ResultSet();
        resultSet.code = CodeEnum.SUCCESS.getCode();
        resultSet.codeMessage = CodeEnum.SUCCESS.getCodeMessage();
        return resultSet;
    }

    //响应成功,有响应数据
    public static <T> ResultSet<T> success(T data) {
        ResultSet resultSet = new ResultSet();
        resultSet.code = CodeEnum.SUCCESS.getCode();
        resultSet.codeMessage = CodeEnum.SUCCESS.getCodeMessage();
        resultSet.data = data;
        return resultSet;
    }

    //响应失败(仅供异常处理器使用,其他地方建议均以抛出异常的方式响应)
    public static ResultSet error(CodeEnum codeEnum, String errorMessage,String errorMessageToUser) {
        ResultSet resultSet = new ResultSet();
        resultSet.code = codeEnum.getCode();
        resultSet.codeMessage = codeEnum.getCodeMessage();
        resultSet.errorMessage = errorMessage;
        resultSet.setErrorMessageToUser(errorMessageToUser);
        return resultSet;
    }


}

2.测试结果集

(1)server.pom中加入公共包的依赖

<dependency>
 <groupId>com.ccm</groupId>
     <artifactId>common</artifactId>
     <version>1.0.0</version>
 </dependency>

(2)server-user服务中编写测试代码

package com.ccm.server.user.controller;

import com.alibaba.fastjson.JSONObject;
import com.ccm.common.exception.result.ResultSet;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 *  @Description 结果集测试
 *  @Author zhouzhiwu
 *  @CreateTime 2020/07/14 17:42
 */
@RestController
@RequestMapping(value = "resultTest")
@Api(tags = "结果集,全局异常处理器,自定义异常测试")
public class ResultTestController {

    @ApiOperation(value = "结果集测试")
    @GetMapping
    public ResultSet<JSONObject> test01() {
        JSONObject vo = new JSONObject();
        vo.put("name","zhouzhiwu");
        vo.put("age","10");
        return ResultSet.success(vo);
    }
}

(3)swagger调用
在这里插入图片描述

三.全局异常处理器

1.概述

(1)全局异常处理器的作用是捕获接口调用是产生的异常,在全局异常处理器中我们可以集中处理.
(2)我们定义了我们自己的结果集,所有的接口调用无论成功与否,http状态码都为200,具体的调用情况依据结果集中的code确定.

2.加入全局异常处理器

(1)为了让所有的微服务能够都使用全局异常处理器,我们将其定义在common包中.
(2)common.pom中加入依赖,主要作用是能够使用springbootweb相关的注解,编译不报错,所以依赖不传递.

<!--springbootweb-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <optional>true</optional><!--代表该依赖是否传递,true=不传递-->
        </dependency>

(3)common中编写全局异常处理器代码,代码中注释完善,所以不做过多的解释.

package com.ccm.common.handler;

import com.ccm.common.exception.CcmMallException;
import com.ccm.common.exception.CustomerException;
import com.ccm.common.exception.ServerException;
import com.ccm.common.exception.result.CodeEnum;
import com.ccm.common.exception.result.ResultSet;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.servlet4preview.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.annotation.PostConstruct;
import javax.validation.ConstraintViolationException;

/**
 * @Description 全局异常处理器
 * @Author zhouzhiwu
 * @CreateTime 2020/7/15 10:13
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 服务名
     */
    @Value("${spring.application.name}")
    private String serverName;

    /**
     * 错误信息前缀
     */
    private String errorMessagePrefix;

    @PostConstruct
    public void init() {
        this.errorMessagePrefix = new StringBuffer().append("(")
                .append(this.serverName)
                .append("服务")
                .append("->")
                .append(") ").toString();
    }
    /**
     * @Description 处理系统内部异常(未知异常,入空指针,索引越界)
     * @Author zhouzhiwu
     * @CreateTime 2020/7/15 10:13
     * @Params [e, request]
     * @Return com.ccm.common.exception.result.ResultSet
     */
    @ExceptionHandler(value = {Exception.class})
    public ResultSet handlerException(Exception e, HttpServletRequest request) {
        log.error("请求路径uri={},系统内部出现异常:{}", request.getRequestURI(),e);
        ResultSet resultSet = ResultSet.error(CodeEnum.SYSTEM_INNER_ERROR,errorMessagePrefix+e.toString(),"服务器繁忙,请稍后再试");
        return resultSet;
    }

   /**
    * @Description 非法请求异常(SpringAOP)
    * @Author zhouzhiwu
    * @CreateTime 2020/7/15 10:14
    * @Params [exception]
    * @Return com.ccm.common.exception.result.ResultSet
    */
    @ExceptionHandler(value = {
                                HttpMediaTypeNotAcceptableException.class,
                                HttpMediaTypeNotSupportedException.class,
                                HttpRequestMethodNotSupportedException.class,
                                MissingServletRequestParameterException.class,
                                NoHandlerFoundException.class,
                                MissingPathVariableException.class,
                                HttpMessageNotReadableException.class
                              })
    public ResultSet handlerSpringAOPException(Exception exception) {
        ResultSet resultSet = ResultSet.error(CodeEnum.ILLEGAL_REQUEST, errorMessagePrefix+exception.getMessage(),exception.getMessage());
        return resultSet;
    }
    
    /**
     * @Description 非法请求异常(@DateTimeFormat注解抛出异常)
     * @Author zhouzhiwu
     * @CreateTime 2020/7/15 10:14
     * @Params [e]
     * @Return com.ccm.common.exception.result.ResultSet
     */
    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    public ResultSet handlerSpringAOPException(MethodArgumentTypeMismatchException e) {
        ResultSet resultSet = ResultSet.error(CodeEnum.ILLEGAL_REQUEST, errorMessagePrefix+e.getMessage(),e.getMessage());
        return resultSet;
    }
    
    /**
     * @Description 非法请求(处理spring validation参数校验抛出异常1)
     * @Author zhouzhiwu
     * @CreateTime 2020/7/15 10:14
     * @Params [methodArgumentNotValidException]
     * @Return com.ccm.common.exception.result.ResultSet
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    public ResultSet handlerMethodArgumentNotValidException(MethodArgumentNotValidException methodArgumentNotValidException) {
        //获取异常字段及对应的异常信息
        StringBuffer stringBuffer = new StringBuffer();
        methodArgumentNotValidException.getBindingResult().getFieldErrors().stream()
                    .map(t -> t.getField()+"=>"+t.getDefaultMessage()+" ")
                    .forEach(e -> stringBuffer.append(e));
        String errorMessage = stringBuffer.toString();
        ResultSet resultSet = ResultSet.error(CodeEnum.ILLEGAL_REQUEST, errorMessagePrefix+errorMessage,errorMessage);
        return resultSet;
    }

    /**
     * @Description 非法请求异常(处理spring validation参数校验抛出异常2)
     * @Author zhouzhiwu
     * @CreateTime 2020/7/15 10:14
     * @Params [constraintViolationException]
     * @Return com.ccm.common.exception.result.ResultSet
     */
    @ExceptionHandler(value = {ConstraintViolationException.class})
    public ResultSet handlerConstraintViolationException(ConstraintViolationException constraintViolationException) {
        String errorMessage = constraintViolationException.getLocalizedMessage();
        ResultSet resultSet = ResultSet.error(CodeEnum.ILLEGAL_REQUEST,errorMessagePrefix+errorMessage,errorMessage);
        return resultSet;
    }

    /**
     * @Description 处理自定义异常-CustomException
     * @Author zhouzhiwu
     * @CreateTime 2020/7/15 10:14
     * @Params [e]
     * @Return com.ccm.common.exception.result.ResultSet
     */
    @ExceptionHandler(value = {CustomerException.class})
    public ResultSet handlerCustomException(CustomerException e) {
        String errorMessage = e.getErrorMessage();
        ResultSet resultSet = ResultSet.error(e.getCodeEnum(), errorMessagePrefix+errorMessage,errorMessage);
        return resultSet;
    }

    /**
     * @Description 处理自定义异常-ServerException
     * @Author zhouzhiwu
     * @CreateTime 2020/7/15 10:14
     * @Params [e]
     * @Return com.ccm.common.exception.result.ResultSet
     */
    @ExceptionHandler(value = {ServerException.class})
    public ResultSet handlerServerException(ServerException e) {
        String errorMessage = e.getErrorMessage();
        ResultSet resultSet = ResultSet.error(e.getCodeEnum(), errorMessagePrefix+errorMessage,errorMessage);
        return resultSet;
    }
}

(4)微服务中加入common包的依赖,server.pom中加入.

		<dependency>
            <groupId>com.ccm</groupId>
            <artifactId>common</artifactId>
            <version>1.0.0</version>
            <optional>true</optional>
        </dependency>

(5)微服务启动类上增加注解扫描的范围,保证能够扫描到全局异常处理器,启动类上加上@ComponentScan(basePackages = “com.ccm”).

package com.ccm.server.user;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 *  @Description server-user服务启动类
 *  @Author zhouzhiwu
 *  @CreateTime 2020/07/03 14:04
 */
@ComponentScan(basePackages = "com.ccm")
@EnableSwagger2
@MapperScan(basePackages = "com.ccm.server.user.dao.mysql.mapper")
@SpringBootApplication //声明为一个启动类
@Import(value = PaginationInterceptor.class)
public class ServerUserApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerUserApplication.class,args);
    }
}


3.全局异常处理器的测试代码见方法test02()、test()03和test04().

package com.ccm.server.user.controller;

import com.alibaba.fastjson.JSONObject;
import com.ccm.common.exception.CustomerException;
import com.ccm.common.exception.result.ResultSet;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 *  @Description 结果集测试
 *  @Author zhouzhiwu
 *  @CreateTime 2020/07/14 17:42
 */
@Validated
@RestController
@RequestMapping(value = "resultTest")
@Api(tags = "结果集,全局异常处理器,自定义异常测试")
public class ResultTestController {

    @ApiOperation(value = "结果集测试")
    @GetMapping
    public ResultSet<JSONObject> test01() {
        JSONObject vo = new JSONObject();
        vo.put("name","zhouzhiwu");
        vo.put("age","10");
        return ResultSet.success(vo);
    }

    @ApiOperation(value = "全局处理器测试-未知异常")
    @GetMapping(value = "test02")
    public ResultSet<JSONObject> test02() {
        JSONObject vo = new JSONObject();
        vo.put("name","zhouzhiwu");
        vo.put("age","10");
        int i = 1/0;
        return ResultSet.success(vo);
    }

    @ApiOperation(value = "全局处理器测试-自定义异常")
    @GetMapping(value = "test03")
    public ResultSet<JSONObject> test03() {
        throw new CustomerException("自定义异常");
    }

    @ApiOperation(value = "全局处理器测试-参数校验框架抛出")
    @GetMapping(value = "test04")
    public ResultSet test04(@Length(min = 1,max = 2) @RequestParam String name) {
        return ResultSet.success(name);
    }
}

4.用swagger测试.

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

源码地址:https://gitee.com/chouchimoo/ccm-mall.git(本章节分支:zj-10)

猜你喜欢

转载自blog.csdn.net/theOldCaptain/article/details/107250320