项目实训5

项目实训5

1. 背景

由于项目需要制定比较统一的返回接口,所以项目中设置了统一的异常返回处理以及统一的返回结果处理。

在实际开发中,如果不处理统一处理异常,那么前端在调用后端提供的接口,就会处理各种的异常结构,对于前端来说那可谓是一场灾难,这对前后端的协作也不友好。比如后端路径:/api/v1/index/user?id=222,如果前端未传入ID,那么SpringBoot就会报如下异常:

{"timestamp":"2021-03-30T15:22:46.139+00:00","status":400,"error":"Bad Request","message":"","path":"/api/v1/index/user"}

对于前端来说,不好定位处理。所以如下代码设置定义:

2. 统一结果处理

首先定义结果枚举类:

package com.example.guke.result;

import lombok.Getter;

@Getter
public enum ResultCodeEnum {

    SUCCESS(true,0,"成功"),
    UNKNOWN_REASON(false,-1,"未知错误"),
    BAD_SQL_GRAMMAR(false,-2,"sql语法错误"),
    JSON_PARSE_ERROR(false,-3,"json解析异常"),
    PARAM_ERROR(false,-4,"参数不正确"),
    ERROR_LOGIN(false,-5,"用户名或密码错误"),
    ERROR_REGISTER(false,-7,"注册失败!"),
    SUCCESS_LOGIN(true,0,"登录成功!"),
    SUCCESS_REGISTER(true,0,"注册成功!"),
    UNKNOWN_USER(false,-8,"用户不存在"),
    NO_TOKEN(false,-10,"无token,请重新登录");

    private final Boolean status; //响应是否成功
    private final Integer code; //返回码
    private final String message; //返回消息

    ResultCodeEnum(Boolean status, Integer code, String message) {
        this.status = status;
        this.code = code;
        this.message = message;
    }
}

枚举类中定义了状态,状态码和相应的信息,同时定义了一些常见的结果信息。

接下来定义Result类

package com.example.guke.result;

import lombok.Data;

import java.util.HashMap;
import java.util.Map;

/**
 * @author niu
 * @Description:
 * @date 2021/5/5 14:34
 */


@Data
public class Result {
    private Boolean success;
    private Integer code;
    private String message;
    private Map<String,Object> data = new HashMap<String ,Object>();

    public Result(){}



    public Result data(Map<String,Object> map){     //给返回值传入data信息,参数为map
        this.setData(map);
        return this;
    }


    public Result data(String key,Object value){    //给返回值传入data信息,参数为键值
        this.data.put(key,value);
        return this;
    }

    public Result message(String message){       //动态设置message
        this.setMessage(message);
        return this;
    }

    public Result code(Integer code){
        this.setCode(code);
        return this;
    }

    public Result success(Boolean success){
        this.setSuccess(success);
        return this;
    }
}

设置了各种类型的构造函数,便于在返回的时候能够便捷地构造。

同时为了符合设计模式中工厂模式的思想,项目中还定义了工厂类:

package com.example.guke.result;


import lombok.Data;

import java.util.Map;

@Data
public class ResultFactory {

    public static Result success(){ //默认的成功返回值
        Result r = new Result();
        r.setCode(ResultCodeEnum.SUCCESS.getCode());
        r.setSuccess(ResultCodeEnum.SUCCESS.getStatus());
        r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
        return r;
    }

    public static Result error(){     //未知错误返回值
        Result r = new Result();
        r.setCode(ResultCodeEnum.UNKNOWN_REASON.getCode());
        r.setSuccess(ResultCodeEnum.UNKNOWN_REASON.getStatus());
        r.setMessage(ResultCodeEnum.UNKNOWN_REASON.getMessage());
        return r;
    }

    public static Result data(Map<String,Object> map){     //给返回值传入data信息,参数为map
        Result r = new Result();
        r.setCode(ResultCodeEnum.SUCCESS.getCode());
        r.setSuccess(ResultCodeEnum.SUCCESS.getStatus());
        r.setData(map);
        return r;
    }

    public static Result message(String message){       //动态设置message
        Result r = new Result();
        r.setCode(ResultCodeEnum.SUCCESS.getCode());
        r.setSuccess(ResultCodeEnum.SUCCESS.getStatus());
        r.setMessage(message);
        return r;
    }


    @Deprecated
    public static Result success(ResultCodeEnum codeEnum){    //传入一个枚举值的成功返回值
        Result r = new Result();
        r.setCode(codeEnum.getCode());
        r.setSuccess(codeEnum.getStatus());
        r.setMessage(codeEnum.getMessage());
        return r;
    }
    @Deprecated
    public static Result error(ResultCodeEnum codeEnum){    //传入一个枚举值的失败返回值
        Result r = new Result();
        r.setCode(codeEnum.getCode());
        r.setSuccess(codeEnum.getStatus());
        r.setMessage(codeEnum.getMessage());
        return r;
    }

    @Deprecated
    public static Result data(String key,Object value){    //给返回值传入data信息,参数为键值
        Result r = new Result();
        r.setCode(ResultCodeEnum.SUCCESS.getCode());
        r.setSuccess(ResultCodeEnum.SUCCESS.getStatus());
//        r.setData(map);
        return r;
    }

}

通过工厂类的静态方法能够快速地构造响应的Result

3. 统一异常处理

其实使用到了@ControllerAdvice注解

是Spring3.2中新增的注解,学名是Controller增强器,作用是给Controller控制器添加统一的操作或处理。

对于@ControllerAdvice,我们比较熟知的用法是结合@ExceptionHandler用于全局异常的处理,但其作用不止于此。ControllerAdvice拆开来就是Controller Advice,关于Advice,在Spring的AOP中,是用来封装一个切面所有属性的,包括切入点和需要织入的切面逻辑。这里ControllerAdvice也可以这么理解,其抽象级别应该是用于对Controller进行切面环绕的,而具体的业务织入方式则是通过结合其他的注解来实现的。@ControllerAdvice是在类上声明的注解,其用法主要有三点:

1.结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。

2.结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的。

3.结合方法型注解@ModelAttribute,表示其注解的方法将会在目标Controller方法执行之前执行。

从上面的讲解可以看出,@ControllerAdvice的用法基本是将其声明在某个bean上,然后在该bean的方法上使用其他的注解来指定不同的织入逻辑。不过这里@ControllerAdvice并不是使用AOP的方式来织入业务逻辑的,而是Spring内置对其各个逻辑的织入方式进行了内置支持。

package com.example.guke.exception;


import com.example.guke.result.Result;
import com.example.guke.result.ResultFactory;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {


    /**
     * @name: getErrorInfoFromException
     * @description: TODO 用来输出具体的报错信息,比e.getMessage()的输出更详细
     * @param e 异常类
     * @return: java.lang.String
     * @date: 2022/5/10 15:13
     * @author: NiuYiq
     *
     */

    public String getErrorInfoFromException(Exception e) {
        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            return "\r\n" + sw.toString() + "\r\n";
//            sw.close();
//            pw.close();
        } catch (Exception e2) {
            return "ErrorInfoFromException";
        }
    }

    /**
     * @name: customException
     * @description: TODO 处理自定义异常,程序中主动抛出的异常
     * @param e
     * @return: com.example.guke.result.Result
     * @date: 2022/5/10 21:29
     * @author: NiuYiq
     *
     */
 
    @ExceptionHandler(value = CustomException.class)
    @ResponseBody
    public Result customException(CustomException e){
        log.error(e.getMessage());
        return ResultFactory.error().message(e.getMessage());
    }

    /**
     * 捕获数据校验异常
     * @param e 错误信息集合
     * @return 错误信息
     */
    /**
     * 处理请求参数格式错误 @RequestBody上使用@Valid 实体上使用@NotNull等,验证失败后抛出的异常是MethodArgumentNotValidException异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Result MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        System.out.println(message);
        return ResultFactory.error().message(message);
    }

    /**
     * 处理请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常
     */
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public Result BindExceptionHandler(BindException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        return ResultFactory.error().message(message);
    }

    /**
     * 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是ConstraintViolationException
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public Result ConstraintViolationExceptionHandler(ConstraintViolationException e) {
        String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());
        return ResultFactory.error().message(message);
    }

    /**
     * 参数格式异常
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    public Result HttpMessageNotReadableExceptionHandler(HttpMessageNotReadableException e) {
        return ResultFactory.error().message("参数格式异常");

    }

    /**
     * @name: exceptionHandler
     * @description: TODO  程序运行中自动抛出的异常
     * @param e
     * @return: com.example.guke.result.Result
     * @date: 2022/5/10 21:30
     * @author: NiuYiq
     *
     */
 

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.CREATED)
    public Result exceptionHandler(Exception e){
        log.error(getErrorInfoFromException(e) + "\n" + e.getMessage());
        return ResultFactory.error().message(getErrorInfoFromException(e)+ "\n" + e.getMessage());
    }
}

本类的代码中写了一个getErrorInfoFromException方法。

定义了不同类型的异常的不同处理方式。

接着是自定义的异常类,继承自Exception

package com.example.guke.exception;

import com.example.guke.result.ResultCodeEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class CustomException extends RuntimeException{
    private int code;
    private String message;

    public CustomException(ResultCodeEnum resultCodeEnum){
        this.code = resultCodeEnum.getCode();
        this.message = resultCodeEnum.getMessage();
    }

    public CustomException(String message){
        this.code = -1;
        this.message = message;
    }
}

这边通过构造方法和前面的ResultCodeEnum结合起来使用了。

于是便完成了全局的统一异常处理和统一结果处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值