## 后台异常处理

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

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

```java
@Getter
public class YamiShopBindException extends RuntimeException{

    /**
     *
     */
    private static final long serialVersionUID = -4137688758944857209L;

    /**
     * http状态码
     */
    private String code;

    private Object object;

    private ServerResponseEntity<?> serverResponseEntity;

    public YamiShopBindException(ResponseEnum responseEnum) {
        super(responseEnum.getMsg());
        this.code = responseEnum.value();
    }
    /**
     * @param responseEnum
     */
    public YamiShopBindException(ResponseEnum responseEnum, String msg) {
        super(msg);
        this.code = responseEnum.value();
    }

    public YamiShopBindException(ServerResponseEntity<?> serverResponseEntity) {
        this.serverResponseEntity = serverResponseEntity;
    }


    public YamiShopBindException(String msg) {
        super(msg);
        this.code = ResponseEnum.SHOW_FAIL.value();
    }

    public YamiShopBindException(String msg, Object object) {
        super(msg);
        this.code = ResponseEnum.SHOW_FAIL.value();
        this.object = object;
    }

}
```

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

```java
public enum ResponseEnum {

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

    /**
     * 用于直接显示提示用户的错误，内容由输入内容决定
     */

    /**
     * 用于直接显示提示系统的成功，内容由输入内容决定
     */
    SHOW_SUCCESS("A00002", ""),

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

    /**
     * 服务器出了点小差
     */
    EXCEPTION("A00005", "服务器出了点小差"),
    /**
     * 方法参数没有校验，内容由输入内容决定
     */
    METHOD_ARGUMENT_NOT_VALID("A00014", "方法参数没有校验");

    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
@Slf4j
@RestController
@RestControllerAdvice
public class DefaultExceptionHandlerConfig {

    @ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class })
    public ResponseEntity<ServerResponseEntity<List<String>>> methodArgumentNotValidExceptionHandler(Exception e) {
        log.error("methodArgumentNotValidExceptionHandler", e);
        List<FieldError> fieldErrors = null;
        if (e instanceof MethodArgumentNotValidException) {
            fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors();
        }
        if (e instanceof BindException) {
            fieldErrors = ((BindException) e).getBindingResult().getFieldErrors();
        }
        if (fieldErrors == null) {
            return ResponseEntity.status(HttpStatus.OK)
                    .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID));
        }

        List<String> defaultMessages = new ArrayList<>(fieldErrors.size());
        for (FieldError fieldError : fieldErrors) {
            defaultMessages.add(fieldError.getField() + ":" + fieldError.getDefaultMessage());
        }
        return ResponseEntity.status(HttpStatus.OK)
                .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, defaultMessages));
    }

    @ExceptionHandler(YamiShopBindException.class)
    public ResponseEntity<ServerResponseEntity<?>> unauthorizedExceptionHandler(YamiShopBindException e){
        log.error("mall4jExceptionHandler", e);

        ServerResponseEntity<?> serverResponseEntity = e.getServerResponseEntity();
        if (serverResponseEntity!=null) {
            return ResponseEntity.status(HttpStatus.OK).body(serverResponseEntity);
        }
        // 失败返回消息 状态码固定为直接显示消息的状态码
        return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(e.getCode(),e.getMessage()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ServerResponseEntity<Object>> exceptionHandler(Exception e){
        log.error("exceptionHandler", e);
        return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(ResponseEnum.EXCEPTION));
    }
}
```



## 前台异常处理



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



```javascript
http.interceptors.response.use(response => {
  return response
}, error => {
  switch (error.response.status) {
    case 400:
      Message.error(error.response.data)
      break
    case 401:
      clearLoginInfo()
      router.push({ name: 'login' })
      break
    case 405:
      Message.error('http请求方式有误')
      break
    case 500:
      Message.error('服务器出了点小差，请稍后再试')
      break
    case 501:
      Message.error('服务器不支持当前请求所需要的某个功能')
      break
  }
  return Promise.reject(error)
})
```

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



##  RESTful 风格

我们的上述代码使用http状态码对请求进行统一响应，其中最大的 

RESTful架构，就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便，所以正得到越来越多网站的采用。

 

[RESTful概述](https://blog.igevin.info/posts/restful-architecture-in-general/)
