項目總結63:使用Spring AOP和BindingResult實現對接口的請求數據校驗,並用@ExceptionHandler返回校驗結果


項目總結63:使用Spring AOP和BindingResult實現對接口的請求數據校驗,並用@ExceptionHandler返回校驗結果

 

問題

  合格的接口,應該在接口的內部對請求參數進行校驗,但是在接口內部通過業務代碼進行校驗,顯得十分冗余,參數越多,代碼就越混亂;

  思考:可以將接口請求參數的校驗封裝成一個全局的方法,進行統一處理。

 

目的

  使用Spring AOP 和 @ExceptionHandler 對接口的數據校驗進行全局封裝,從而做到只要在請求數據的實體類中進行注解說明,就可以進行數據校驗;

  具體可以:

    1- 避免在接口內部,通過代碼方式進行冗余的數據校驗;比如:if(data is empty){retur ...}

    2- 可以在請求數據的實體類中進行注解說明,比如:@NotEmpty(message = "手機號不能為空");就可以進行數據校驗;

    3- 可以將具體的校驗結果直接返回給前端;比如: {...,"msg": "手機號不能為空",...}

 

解決思路

  1- 用@Valid 和 BindingResult分析請求參數校驗情況

  2- 用AOP對BindingResult中的校驗結果進行處理,如果校驗出問題,拋出異常;

  3- 使用@ExceptionHandler注解捕獲校驗異常,並返回給前端

 

具體實現方案(源碼示例)(以修改登陸密碼接口為例)

第一步:用@Valid 和 BindingResult分析請求參數校驗

具體邏輯:被@Validated的實體類,會自動根據實體類中的參數的@NotEmpty注解,進行數據校驗;並將數據校驗結果封裝到BindingResult類中;如果檢驗有問題,BindingResult數據如下

 

 

  源碼如下:

  1- controller層接口

 
         
import javax.validation.Valid;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

@RestController @RequestMapping(value
="/api") public class ApiLoginController extends ApiBaseController { @ValidAnn //AOP切入點 @PostMapping(value="/password/update") public ResultModel<Object> updatePassword(@Valid @RequestBody UpdatePasswordReq updatePasswordReq, BindingResult bindingResult){ //1-校驗驗證碼 //2-更新密碼 return ResultUtil.success(); } }

  2- 請求數據實體類

//使用了lombok
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UpdatePasswordReq {

    @NotEmpty(message = "手機號不能為空")
    private String mobile;

    @NotEmpty(message = "新密碼1不能為空")
    private String newPassword;

    @NotEmpty(message = "新密碼2不能為空")
    private String newPasswordAgain;

    @NotEmpty(message = "驗證碼不能為空")
    private String code;
}

 

  3- 返回參數實體類

@Data
public class ResultModel<T> {

    // Http網絡碼
    private Integer code;
    // 提示消息
    private String  msg;
    // 數據
    private T data;
}

 

  4- 返回結果通用類和異常枚舉

/**
 * 工具類
 *
 * @author cjm
 * @date 2018/2/1
 */
public class ResultUtil {

    /**
     * 返回異常信息
     */
    public static ResultModel exception(ResultEnum enums) {
        ResultModel model = new ResultModel();
        model.setCode(enums.getCode());
        model.setMsg(enums.getMessage());
        return model;
    }
    /**
     * 返回成功信息(只包含提示語)
     */
    public static ResultModel success() {
        ResultModel model = new ResultModel();
        model.setCode(ResultEnum.SUCCESS.getCode());
        model.setMsg(ResultEnum.SUCCESS.getMessage());
        return model;
    }


}


public enum ResultEnum {
    SUCCESS(200, "success"), 
    FAIL(400, "fail"), 
    VISIT_NOT_AUTHORIZED(401, "未授權"), 
    ARGUMENT_ERROR(402,"參數錯誤"), 
    ARGUMENT_EXCEPTION(407, "參數存在異常"), 
    ARGUMENT_TOKEN_EMPTY(409, "Token為空"), 
    ARGUMENT_TOKEN_INVALID(410, "Token無效"), 
    SERVER_ERROR(501, "服務端異常"), 
    SERVER_SQL_ERROR(503,"數據庫操作出現異常"), 
    SERVER_DATA_REPEAT(504, "服務器數據已存在"), 
    SERVER_DATA_NOTEXIST(505,"數據不存在"), 
    SERVER_DATA_STATUS_ERROR(506, "數據狀態錯誤"), 
    SERVER_SMS_SEND_FAIL(701, "短信發送失敗"),


    ;
    


    private int code;
    private String message;

    private ResultEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

 

第二步:用AOP對BindingResult中的校驗結果進行處理,如果校驗出問題,拋出異常;

具體實現:AOP根據@ValidAnn定位到每一個需要數據校驗的接口,使用環繞通知,處理BindingResult類的結果,如果數據校驗有問題,將校驗結果交給ServerException,並且拋出ServerException異常

  源碼如下

  1- ValidAnn 注解,用戶AOP定位

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidAnn {

}

 

  2- 環繞AOP類,處理判斷並處理校驗結果,如果校驗出問題,則拋出ServerException;

@Aspect
@Component
@Order(1)
public class ValidAop {
    /**
     * 所有controller方法都會執行此切面
     * 用於檢驗參數
     */
    @Pointcut("@annotation(com.hs.annotation.ValidAnn)")
    public void validAop() {
    }

    /**
     * 對切面進行字段驗證
     *
     * @param joinPoint 切點
     * @return Object
     * @throws Throwable
     */
    @Around("validAop()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        List<String> errorList = new ArrayList<>();
        if (args != null) {
            Arrays.stream(args)
                    .forEach(arg -> {
                        if (arg instanceof BindingResult) {
                            BindingResult result = (BindingResult) arg;
                            if (result.hasErrors()) {
                                result.getAllErrors()
                                        .forEach(err -> {
                                            errorList.add(err.getDefaultMessage());
                                        });
                                throw new ServerException(Joiner.on(" || ").join(errorList));
                            }
                        }
                    });
        }
        return joinPoint.proceed();
    }
}

 

 

  3- ServerException,當校驗出問題時,拋出當前異常

public class ServerException extends RuntimeException {

    private Integer code;

    private Object Data;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Object getData() {
        return Data;
    }

    public void setData(Object data) {
        Data = data;
    }

    public ServerException() {
        super();
    }

    public ServerException(Integer code, String message, Object data) {
        super(message);
        this.code = code;
        this.Data = data;
    }

    public ServerException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public ServerException(String message) {
        super(message);
        this.code = ResultEnum.ARGUMENT_ERROR.getCode();
    }

    public ServerException(String message, Throwable cause) {
        super(message, cause);
    }

    public ServerException(Throwable cause) {
        super(cause);
    }

    protected ServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

 

 

第三步:使用@ExceptionHandler注解捕獲校驗異常,並返回給前端

具體實現:@ControllerAdvice和@ExceptionHandler攔截異常並統一處理。在GlobalExceptionHandler 類中使用@ExceptionHandler(value = ServerException.class)專門捕獲ServerException異常,並且將結果封裝到返回類中,返回給前端

  源碼如下:

  1- GlobalExceptionHandler 類,用於捕獲異常,並作相關處理

@ControllerAdvice //使用 @ControllerAdvice 實現全局異常處理
@ResponseBody
public class GlobalExceptionHandler {
    
    protected Logger logger = Logger.getLogger(this.getClass());


    /**
     * 自定義異常捕獲
     *
     * @param exception 捕獲的異常
     * @return 返回信息
     */
    @ExceptionHandler(value = ServerException.class)
    public ResultModel exceptionHandler(ServerException exception) {
        logger.warn(exception);
        ResultModel model = new ResultModel();
        model.setCode(exception.getCode());
        model.setMsg(exception.getMessage());
        model.setData(exception.getData());
        //3-以Json格式返回數據
        return model;
    }
}

 

 

測試結果

 

 

 

 

 

END


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM