## 统一异常处理

### 后端异常处理

在开发过程中，不可避免的是需要处理各种异常，异常处理方法随处可见，所以代码中就会出现大量的`try {...} catch {...} finally {...}` 代码块，不仅会造成大量的冗余代码，而且还影响代码的可读性，所以对异常统一处理非常有必要。为此，我们定义了一个统一的异常类`mall4cloudException` 与异常管理类 `DefaultExceptionHandlerConfig`。

我们先来看下 `mall4cloudException`的代码

```java
public class mall4cloudException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	private Object object;
    
	/**
	 * 响应状态码枚举
	 */
	private ResponseEnum responseEnum;

	public mall4cloudException(String msg) {
		super(msg);
	}

	public mall4cloudException(String msg, Object object) {
		super(msg);
		this.object = object;
	}

	public mall4cloudException(String msg, Throwable cause) {
		super(msg, cause);
	}


	public mall4cloudException(ResponseEnum responseEnum) {
		super(responseEnum.getMsg());
		this.responseEnum = responseEnum;
	}

	public mall4cloudException(ResponseEnum responseEnum,Object object) {
		super(responseEnum.getMsg());
		this.responseEnum = responseEnum;
		this.object = object;
	}


	public Object getObject() {
		return object;
	}

	public ResponseEnum getResponseEnum() {
		return responseEnum;
	}
}
```

`ResponseEnum`为我们自定义的返回状态码的枚举类，定义为一个枚举类，更直观处理异常返回的状态码及异常内容，以后每增加一种异常情况，只需增加一个枚举实例即可，不用每一种异常都定义一个异常类。

```java
public enum ResponseEnum {

	/**
	 * ok
	 */
	OK("00000", "ok"),

	/**
	 * 用于直接显示提示用户的错误，内容由输入内容决定
	 */
	SHOW_FAIL("A00001", ""),

	/**
	 * 方法参数没有校验，内容由输入内容决定
	 */
	METHOD_ARGUMENT_NOT_VALID("A00002", ""),

	/**
	 * 无法读取获取请求参数
	 */
	HTTP_MESSAGE_NOT_READABLE("A00003", "请求参数格式有误"),

	/**
	 * 未授权
	 */
	UNAUTHORIZED("A00004", "Unauthorized"),

	/**
	 * 服务器出了点小差
	 */
	EXCEPTION("A00005", "服务器出了点小差");
    
    private final String code;

	private final String msg;

	public String value() {
		return code;
	}

	public String getMsg() {
		return msg;
	}

	ResponseEnum(String code, String msg) {
		this.code = code;
		this.msg = msg;
	}

	@Override
	public String toString() {
		return "ResponseEnum{" + "code='" + code + '\'' + ", msg='" + msg + '\'' + "} " + super.toString();
	}

}
```

再来看看 `DefaultExceptionHandlerConfig`类

```java
@RestController
@RestControllerAdvice
public class DefaultExceptionHandlerConfig {

	private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionHandlerConfig.class);

	@ExceptionHandler(mall4cloudException.class)
	public ResponseEntity<ServerResponseEntity<Object>> mall4cloudExceptionHandler(mall4cloudException e) {
		logger.error("mall4cloudExceptionHandler", e);

		ResponseEnum responseEnum = e.getResponseEnum();
		// 失败返回失败消息 + 状态码
		if (responseEnum != null) {
			return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(responseEnum, e.getObject()));
		}
		// 失败返回消息 状态码固定为直接显示消息的状态码
		return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.showFailMsg(e.getMessage()));
	}
}
```

---

### 前端异常处理

前端请求与相应做了封装，请求响应的内容会被拦截器所拦截，当后台返回给前台特定的状态码，前台将显示不同报错信息。请求响应非常常见，我们查看在`src\utils\request.js`里面的其中一段代码

```javascript
service.interceptors.response.use(
  response => {
    const res = response.data

    if (res.code === '00000') {
      return res.data
    }

    // A00001 用于直接显示提示用户的错误，内容由输入内容决定
    // A00003 无法读取获取请求参数
    if (res.code === 'A00001' || res.code === 'A00003' || res.code === 'A00005') {
      Message({
        message: res.msg || 'Error',
        type: 'error',
        duration: 1.5 * 1000
      })
      return Promise.reject(res)
    }

    // A00002 方法参数没有校验，内容由输入内容决定
    if (res.code === 'A00002') {
      if (res.data && res.data.length) {
        res.data.forEach(errorMsg => {
          Message({
            message: errorMsg || 'Error',
            type: 'error',
            duration: 1.5 * 1000
          })
        })
      } else {
        Message({
          message: res.msg || 'Error',
          type: 'error',
          duration: 1.5 * 1000
        })
      }
      return Promise.reject()
    }

    // A00004 未授权
    if (res.code === 'A00004') {
      // to re-login
      MessageBox.confirm('您已注销，您可以取消停留在该页上，或重新登录', '确认注销', {
        confirmButtonText: '重新登陆',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        store.dispatch('user/resetToken').then(() => {
          location.reload()
        })
      })
      return Promise.reject()
    }
    return Promise.reject(res)
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 1.5 * 1000
    })
    return Promise.reject(error)
  }
)
```

这里将会统一拦截返回的状态码如`A00001`，进行错误提示。
