springboot优雅封装全局异常处理

这次分享的内容整合了之前分享的关于springboot国际化和sa-token的SaResult的重新封装的内容,如果有兴趣可以去看一下前面的内容,关于国际化和SaResult还有SaCode的代码在前面的内容里。

下面我们用一个注册的接口做测试写完成今天的分享内容,好了开始今天的代码时间:

一、先来写一个注册用户的实体类,注意在属性上方的校验注解如@NotBlank,@Length,@Pattern等,具体要怎么用要另外分享了,包括分组的用法,这里不多说了。

package com.chhuang.bean;

import com.chhuang.core.valid.ValidationGroups;
import com.chhuang.utils.string.RegexpUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.io.Serializable;

/**
 * @ClassName RegeditBean
 * @Description 用于注册用户实体类
 * @Author Darren Huang
 * @Date 2022/11/19 20:57
 * @Version 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "RegeditAccountBean实体类", description = "用于注册用户实体类")
public class RegeditAccountBean implements Serializable {
    private static final long serialVersionUID = -6131278519375255021L;

    /**
     * 用户名
     * public String register(@Validated(ValidationGroups.RegeditGroup.class) @RequestBody User user)
     */
    @NotBlank(message = "{username.not_blank}", groups = ValidationGroups.RegeditGroup.class)
    @Length(min = 1, max = 16, message = "{username.over_max_len}", groups = ValidationGroups.RegeditGroup.class)
    @ApiModelProperty(value = "用户名", required = true)
    private String username;

    /**
     * 密码
     */
    @NotBlank(message = "{password.not_blank}", groups = ValidationGroups.RegeditGroup.class)
    @Pattern(regexp=RegexpUtils.PASSWORD_WITH_NUM_AND_LET_SYM, message = "{password.valid_fail}",groups = ValidationGroups.RegeditGroup.class)
    @ApiModelProperty(value = "密码", required = true)
    private String password;

    /**
     * 昵称
     */
    @Length(min = 1, max = 16, message = "{nickname.over_max_len}", groups = ValidationGroups.RegeditGroup.class)
    @ApiModelProperty(value = "昵称")
    private String nickname;

    /**
     * 姓
     */
    @ApiModelProperty(value = "姓")
    private String lastname;

    /**
     * 名
     */
    @ApiModelProperty(value = "名")
    private String firstname;

    /**
     * 性别:男1,女0
     */
    @ApiModelProperty(value = "性别:男1,女0")
    private Byte gender;

    /**
     * 手机号
     */
    @NotBlank(message = "{phone.not_blank}", groups = ValidationGroups.RegeditGroup.class)
    @Pattern(regexp = RegexpUtils.MOBILE_PHONE_REGEXP, message = "{phone.valid_fail}", groups = ValidationGroups.RegeditGroup.class)
    @ApiModelProperty(value = "手机号", required = true)
    private String phone;

    /**
     * 电子邮箱号
     */
    @NotBlank(message = "{email.not_blank}", groups = ValidationGroups.RegeditGroup.class)
    @Email(message = "{email.valid_fail}", groups = ValidationGroups.RegeditGroup.class)
    @ApiModelProperty(value = "电子邮箱号", required = true)
    private String email;

    /**
     * 头像
     */
    @ApiModelProperty(value = "头像")
    private String photo;

    /**
     * 验证码
     */
    @Valid //这里需要做嵌套校验
    @ApiModelProperty(value = "验证码对象", required = true)
    private CaptchaBean captcha;
}

二、下面是我们今天的另外一位配角,自定义的业务异常,比如注册时,用户名已存在了,直接抛出这个异常就可以了,或者手机号,邮箱等已存在都直接抛这个异常,当然new 的时候带的参数不一样就是SaCode不一样就可以了。上代码:

package com.chhuang.core.exception;

import com.chhuang.core.enums.SaCode;
import lombok.Getter;

/**
 * @ClassName SaException
 * @Description 自定义业务异常类
 * @Author Darren Huang
 * @Date 2022/11/25 22:59
 * @Version 1.0
 */
@Getter
public class SaException extends RuntimeException {
    private SaCode saCode;

    public SaException(SaCode saCode){
        super(saCode.getMessage());
        this.saCode = saCode;
    }

}

三、下面是我们今天真正的主角,全局异常处理类,先权限校验,然后参数校验,再业务,最后一个兜底。就是具体什么异常给前端什么反馈我们都自己封装起来,统一好看。

package com.chhuang.handle;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.SaTokenException;
import com.chhuang.component.I18nMessage;
import com.chhuang.core.enums.SaCode;
import com.chhuang.core.exception.SaException;
import com.chhuang.core.vo.SaResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolationException;

/**
 * @ClassName GlobalExceptionHandler
 * @Description 全局异常处理类,先权限校验,然后参数校验,再业务,最后一个兜底
 * @Author Darren Huang
 * @Date 2022/11/25 22:51
 * @Version 1.0
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 获取国际化资源组件
     */
    @Autowired
    private I18nMessage i18nMessage;

    /**
     * sa权限验证异常
     * @param e
     * @return
     */
    @ExceptionHandler(SaTokenException.class)
    public SaResult handle(SaTokenException e) {
        log.error("SaTokenException::{}", e.getLocalizedMessage());
        if(e instanceof NotLoginException){
            return SaResult.get(SaCode.NOT_LOGIN.getCode(), i18nMessage.get(SaCode.NOT_LOGIN.getCode()));
        }
        return SaResult.get(SaCode.NOT_AUTH.getCode(), i18nMessage.get(SaCode.NOT_AUTH.getCode()));
    }

    /**
     * get请求中的参数校验
     * @param exception
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public SaResult handle(ConstraintViolationException exception){
        StringBuffer message = new StringBuffer();
        exception.getConstraintViolations().forEach(e->message.append(e.getMessage()).append(","));
        String msg = message.substring(0, message.length()-1);
        log.error("ConstraintViolationException::{}", msg);
        return SaResult.get(SaCode.PARAMS_VALID_FAIL.getCode(), msg, null);
    }
    /**
     * form-data格式的参数校验
     * @param exception
     * @return
     */
    @ExceptionHandler(BindException.class)
    public SaResult handle(BindException exception){
        StringBuffer message = new StringBuffer();
        exception.getAllErrors().forEach(e->message.append(e.getDefaultMessage()).append(","));
        String msg = message.substring(0, message.length()-1);
        log.error("BindException::{}", msg);
        return SaResult.get(SaCode.PARAMS_VALID_FAIL.getCode(), msg, null);
    }

    /**
     * json格式的参数校验
     * @param exception
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public SaResult handle(MethodArgumentNotValidException exception) {
        StringBuffer message = new StringBuffer();
        exception.getBindingResult().getAllErrors().forEach(e->message.append(e.getDefaultMessage()).append(","));
        String msg = message.substring(0, message.length()-1);
        log.error("MethodArgumentNotValidException::{}", msg);
        return SaResult.get(SaCode.PARAMS_VALID_FAIL.getCode(), msg, null);
    }

    /**
     * 业务异常
     * @param e
     * @return
     */
    @ExceptionHandler(SaException.class)
    public SaResult handle(SaException e) {
        log.error("SaException::{}", e.getLocalizedMessage());
        return SaResult.get(e.getSaCode().getCode(), i18nMessage.get(e.getSaCode().getCode()));
    }

    /**
     * 兜底的一个异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public SaResult handle(Exception e) {
        log.error("Exception::{}", e.getLocalizedMessage());
        return SaResult.error(i18nMessage.get(SaCode.INTERNAL_SERVER_ERROR.getCode()));
    }

}

四、我们简单写一个controller来测试一下吧,regeditAccount对象会根据RegeditAccountBean类中属性上方的校验要求对属性的值进行校验,有误就自动抛出异常,然后我们的全局异常处理类GlobalExceptionHandler就会自动封装返回内容,这里因为是json如果有误,会执行@ExceptionHandler(MethodArgumentNotValidException.class)中的方法返回结果。

    /**
     * 用户注册接口
     * @param regeditAccount
     * @return
     */
    @PostMapping
    @ApiOperation(value = "用户注册接口", notes = "用户注册接口")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "用户注册成功")
    })
    public SaResult regedit(@Validated(ValidationGroups.RegeditGroup.class) @RequestBody RegeditAccountBean regeditAccount){
        SaResult saResult;

        if("admin".equals(regeditAccount.getUsername()))
            throw new SaException(SaCode.USERNAME_ALREADY_EXISTS);

        return null;
    }

看一下,执行结果吧

springboot优雅封装全局异常处理_第1张图片

 很厉害吧,另外 在controller方法,写了另外 一个手动抛出的业务异常SaException,就是我上面说是如果用户名已存在,我们就可以手动这样抛出异常。

最后代码中用到的,比如SaCode,I18nMessage,SaResult等如何想知道内容,可以看我前面写的关于springboot国际化和sa-token的分享文章,里面有代码。全部的代码我会在后面分享在gitee上。因为时间长了我也会忘,我分享内容其实主要是给我自己以后看的。

你可能感兴趣的:(全局异常处理,spring,boot,java,spring)