这次分享的内容整合了之前分享的关于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;
}
看一下,执行结果吧
很厉害吧,另外 在controller方法,写了另外 一个手动抛出的业务异常SaException,就是我上面说是如果用户名已存在,我们就可以手动这样抛出异常。
最后代码中用到的,比如SaCode,I18nMessage,SaResult等如何想知道内容,可以看我前面写的关于springboot国际化和sa-token的分享文章,里面有代码。全部的代码我会在后面分享在gitee上。因为时间长了我也会忘,我分享内容其实主要是给我自己以后看的。