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項目全套學習教程連載中,關注公衆號第一時間獲取。