SpringBoot实战电商项目mall(30k+star)地址:github.com/macrozheng/…java
平时在开发接口的时候,经常会须要对参数进行校验,这里提供两种处理校验逻辑的方式。一种是使用Hibernate Validator来处理,另外一种是使用全局异常来处理,下面咱们讲下这两种方式的用法。git
Hibernate Validator是SpringBoot内置的校验框架,只要集成了SpringBoot就自动集成了它,咱们能够经过在对象上面使用它提供的注解来完成参数校验。github
咱们先来了解下经常使用的注解,对Hibernate Validator所提供的校验功能有个印象。正则表达式
接下来咱们以添加品牌接口的参数校验为例来说解下Hibernate Validator的使用方法,其中涉及到一些AOP的知识,不了解的朋友能够参考下《SpringBoot应用中使用AOP记录接口访问日志》。数据库
PmsBrandParam
中添加校验注解,用于肯定属性的校验规则及校验失败后须要返回的信息;/** * 品牌传递参数 * Created by macro on 2018/4/26. */
public class PmsBrandParam {
@ApiModelProperty(value = "品牌名称",required = true)
@NotEmpty(message = "名称不能为空")
private String name;
@ApiModelProperty(value = "品牌首字母")
private String firstLetter;
@ApiModelProperty(value = "排序字段")
@Min(value = 0, message = "排序最小为0")
private Integer sort;
@ApiModelProperty(value = "是否为厂家制造商")
@FlagValidator(value = {"0","1"}, message = "厂家状态不正确")
private Integer factoryStatus;
@ApiModelProperty(value = "是否进行显示")
@FlagValidator(value = {"0","1"}, message = "显示状态不正确")
private Integer showStatus;
@ApiModelProperty(value = "品牌logo",required = true)
@NotEmpty(message = "品牌logo不能为空")
private String logo;
@ApiModelProperty(value = "品牌大图")
private String bigPic;
@ApiModelProperty(value = "品牌故事")
private String brandStory;
//省略若干Getter和Setter方法...
}
复制代码
/** * 品牌功能Controller * Created by macro on 2018/4/26. */
@Controller
@Api(tags = "PmsBrandController", description = "商品品牌管理")
@RequestMapping("/brand")
public class PmsBrandController {
@Autowired
private PmsBrandService brandService;
@ApiOperation(value = "添加品牌")
@RequestMapping(value = "/create", method = RequestMethod.POST)
@ResponseBody
public CommonResult create(@Validated @RequestBody PmsBrandParam pmsBrand, BindingResult result) {
CommonResult commonResult;
int count = brandService.createBrand(pmsBrand);
if (count == 1) {
commonResult = CommonResult.success(count);
} else {
commonResult = CommonResult.failed();
}
return commonResult;
}
}
复制代码
/** * HibernateValidator错误结果处理切面 * Created by macro on 2018/4/26. */
@Aspect
@Component
@Order(2)
public class BindingResultAspect {
@Pointcut("execution(public * com.macro.mall.controller.*.*(..))")
public void BindingResult() {
}
@Around("BindingResult()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof BindingResult) {
BindingResult result = (BindingResult) arg;
if (result.hasErrors()) {
FieldError fieldError = result.getFieldError();
if(fieldError!=null){
return CommonResult.validateFailed(fieldError.getDefaultMessage());
}else{
return CommonResult.validateFailed();
}
}
}
}
return joinPoint.proceed();
}
}
复制代码
name
字段,就会返回名称不能为空
的错误信息;有时候框架提供的校验注解并不能知足咱们的须要,此时咱们就须要自定义校验注解。好比仍是上面的添加品牌,此时有个参数
showStatus
,咱们但愿它只能是0或者1,不能是其余数字,此时可使用自定义注解来实现该功能。app
/** * 用户验证状态是否在指定范围内的注解 * Created by macro on 2018/4/26. */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Constraint(validatedBy = FlagValidatorClass.class)
public @interface FlagValidator {
String[] value() default {};
String message() default "flag is not found";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
复制代码
/** * 状态标记校验器 * Created by macro on 2018/4/26. */
public class FlagValidatorClass implements ConstraintValidator<FlagValidator,Integer> {
private String[] values;
@Override
public void initialize(FlagValidator flagValidator) {
this.values = flagValidator.value();
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
boolean isValid = false;
if(value==null){
//当状态为空时使用默认值
return true;
}
for(int i=0;i<values.length;i++){
if(values[i].equals(String.valueOf(value))){
isValid = true;
break;
}
}
return isValid;
}
}
复制代码
/** * 品牌传递参数 * Created by macro on 2018/4/26. */
public class PmsBrandParam {
@ApiModelProperty(value = "是否进行显示")
@FlagValidator(value = {"0","1"}, message = "显示状态不正确")
private Integer showStatus;
//省略若干Getter和Setter方法...
}
复制代码
showStatus=3
,会返回显示状态不正确
的错误信息。这种方式的优势是可使用注解来实现参数校验,不须要一些重复的校验逻辑,可是也有一些缺点,好比须要在Controller的方法中额外注入一个BindingResult对象,只支持一些简单的校验,涉及到要查询数据库的校验就没法知足了。框架
使用全局异常处理来处理校验逻辑的思路很简单,首先咱们须要经过@ControllerAdvice注解定义一个全局异常的处理类,而后自定义一个校验异常,当咱们在Controller中校验失败时,直接抛出该异常,这样就能够达到校验失败返回错误信息的目的了。ide
@ControllerAdvice:相似于@Component注解,能够指定一个组件,这个组件主要用于加强@Controller注解修饰的类的功能,好比说进行全局异常处理。工具
@ExceptionHandler:用来修饰全局异常处理的方法,能够指定异常的类型。学习
ApiException
,当咱们校验失败时抛出该异常:/** * 自定义API异常 * Created by macro on 2020/2/27. */
public class ApiException extends RuntimeException {
private IErrorCode errorCode;
public ApiException(IErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public ApiException(String message) {
super(message);
}
public ApiException(Throwable cause) {
super(cause);
}
public ApiException(String message, Throwable cause) {
super(message, cause);
}
public IErrorCode getErrorCode() {
return errorCode;
}
}
复制代码
Asserts
,用于抛出各类ApiException
;/** * 断言处理类,用于抛出各类API异常 * Created by macro on 2020/2/27. */
public class Asserts {
public static void fail(String message) {
throw new ApiException(message);
}
public static void fail(IErrorCode errorCode) {
throw new ApiException(errorCode);
}
}
复制代码
GlobalExceptionHandler
,用于处理全局异常,并返回封装好的CommonResult对象;/** * 全局异常处理 * Created by macro on 2020/2/27. */
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(value = ApiException.class)
public CommonResult handle(ApiException e) {
if (e.getErrorCode() != null) {
return CommonResult.failed(e.getErrorCode());
}
return CommonResult.failed(e.getMessage());
}
}
复制代码
/** * 用户优惠券管理Controller * Created by macro on 2018/8/29. */
@Controller
@Api(tags = "UmsMemberCouponController", description = "用户优惠券管理")
@RequestMapping("/member/coupon")
public class UmsMemberCouponController {
@Autowired
private UmsMemberCouponService memberCouponService;
//改进前
@ApiOperation("领取指定优惠券")
@RequestMapping(value = "/add/{couponId}", method = RequestMethod.POST)
@ResponseBody
public CommonResult add(@PathVariable Long couponId) {
return memberCouponService.add(couponId);
}
//改进后
@ApiOperation("领取指定优惠券")
@RequestMapping(value = "/add/{couponId}", method = RequestMethod.POST)
@ResponseBody
public CommonResult add(@PathVariable Long couponId) {
memberCouponService.add(couponId);
return CommonResult.success(null,"领取成功");
}
}
复制代码
/** * 用户优惠券管理Service * Created by macro on 2018/8/29. */
public interface UmsMemberCouponService {
/** * 会员添加优惠券(改进前) */
@Transactional
CommonResult add(Long couponId);
/** * 会员添加优惠券(改进后) */
@Transactional
void add(Long couponId);
}
复制代码
/** * 会员优惠券管理Service实现类 * Created by macro on 2018/8/29. */
@Service
public class UmsMemberCouponServiceImpl implements UmsMemberCouponService {
@Autowired
private UmsMemberService memberService;
@Autowired
private SmsCouponMapper couponMapper;
@Autowired
private SmsCouponHistoryMapper couponHistoryMapper;
@Autowired
private SmsCouponHistoryDao couponHistoryDao;
//改进前
@Override
public CommonResult add(Long couponId) {
UmsMember currentMember = memberService.getCurrentMember();
//获取优惠券信息,判断数量
SmsCoupon coupon = couponMapper.selectByPrimaryKey(couponId);
if(coupon==null){
return CommonResult.failed("优惠券不存在");
}
if(coupon.getCount()<=0){
return CommonResult.failed("优惠券已经领完了");
}
Date now = new Date();
if(now.before(coupon.getEnableTime())){
return CommonResult.failed("优惠券还没到领取时间");
}
//判断用户领取的优惠券数量是否超过限制
SmsCouponHistoryExample couponHistoryExample = new SmsCouponHistoryExample();
couponHistoryExample.createCriteria().andCouponIdEqualTo(couponId).andMemberIdEqualTo(currentMember.getId());
long count = couponHistoryMapper.countByExample(couponHistoryExample);
if(count>=coupon.getPerLimit()){
return CommonResult.failed("您已经领取过该优惠券");
}
//省略领取优惠券逻辑...
return CommonResult.success(null,"领取成功");
}
//改进后
@Override
public void add(Long couponId) {
UmsMember currentMember = memberService.getCurrentMember();
//获取优惠券信息,判断数量
SmsCoupon coupon = couponMapper.selectByPrimaryKey(couponId);
if(coupon==null){
Asserts.fail("优惠券不存在");
}
if(coupon.getCount()<=0){
Asserts.fail("优惠券已经领完了");
}
Date now = new Date();
if(now.before(coupon.getEnableTime())){
Asserts.fail("优惠券还没到领取时间");
}
//判断用户领取的优惠券数量是否超过限制
SmsCouponHistoryExample couponHistoryExample = new SmsCouponHistoryExample();
couponHistoryExample.createCriteria().andCouponIdEqualTo(couponId).andMemberIdEqualTo(currentMember.getId());
long count = couponHistoryMapper.countByExample(couponHistoryExample);
if(count>=coupon.getPerLimit()){
Asserts.fail("您已经领取过该优惠券");
}
//省略领取优惠券逻辑...
}
}
复制代码
优惠券不存在
的错误信息。使用全局异常来处理校验逻辑的优势是比较灵活,能够处理复杂的校验逻辑。缺点是咱们须要重复编写校验代码,不像使用Hibernate Validator那样只要使用注解就能够了。不过咱们能够在上面的Asserts
类中添加一些工具方法来加强它的功能,好比判断是否为空和判断长度等均可以本身实现。
咱们能够两种方法一块儿结合使用,好比简单的参数校验使用Hibernate Validator来实现,而一些涉及到数据库操做的复杂校验使用全局异常处理的方式来实现。
mall项目全套学习教程连载中,关注公众号第一时间获取。