總結篇-后臺參數(shù)驗證的幾種方式(轉載)

轉載自:https://blog.csdn.net/m0_37499059/article/details/81431562?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase

1.前言
參數(shù)驗證是一個常見的問題,無論是前端還是后臺,都需對用戶輸入進行驗證,以此來保證系統(tǒng)數(shù)據(jù)的正確性。對于web來說,有些人可能理所當然的想在前端驗證就行了,但這樣是非常錯誤的做法,前端代碼對于用戶來說是透明的,稍微有點技術的人就可以繞過這個驗證,直接提交數(shù)據(jù)到后臺。無論是前端網(wǎng)頁提交的接口,還是提供給外部的接口,參數(shù)驗證隨處可見,也是必不可少的。前端做驗證只是為了用戶體驗,比如控制按鈕的顯示隱藏,單頁應用的路由跳轉等等。后端才是最終的保障。總之,一切用戶的輸入都是不可信的。

2.常見的驗證的方式
前端的校驗是必須的,這個很簡單,因為客戶體驗。后臺的校驗更是必須的,關鍵在于如何與目前我們的分層思想(控制層、業(yè)務層、持久層)綜合起來考慮。在每層都要進行校驗嗎?還是只在是某個特定層做就可以了?是否有好的校驗框架(如前端的jquery校驗框架、springmvc校驗框架)?總之校驗框架還是有很多的,原理不就是對后端接收的數(shù)據(jù)進行特定規(guī)則的判斷,那我們怎么制定規(guī)則,有怎么去檢驗呢?

1、表現(xiàn)層驗證:SpringMVC提供對JSR-303的表現(xiàn)層驗證;
2、業(yè)務邏輯層驗證:Spring3.1提供對業(yè)務邏輯層的方法驗證(當然方法驗證可以出現(xiàn)在其他層,但筆者覺得方法驗證應該驗證業(yè)務邏輯);
3、DAO層驗證:Hibernate提供DAO層的模型數(shù)據(jù)的驗證(可參考hibernate validator參考文檔的7.3. ORM集成)。
4、數(shù)據(jù)庫端的驗證:通過數(shù)據(jù)庫約束來進行;
5、客戶端驗證支持:JSR-303也提供編程式驗證支持。

1)通過 if-if 判斷

if(string.IsNullOrEmpty(info.UserName))
{
    return FailJson("用戶名不能為空");
}

逐個對參數(shù)進行驗證,這種方式最粗暴.如果參數(shù)一多,就要寫n多的if-if,相當繁瑣,更重要的是這部分判斷沒法重用,另一個方法又是這樣判斷.。

2) 自定義注解實現(xiàn)參數(shù)校驗
切面攔截controller方法,然后捕獲帶@CheckParam注解方法參數(shù)實例,最后反射實例校驗。
controller:

@RequestMapping(value = "update" )
@ResponseBody
public ResultBean update(@CheckParam User user){
    return ResultBean.ok();
}

model:

public class User implements Serializable{
    @CheckParam(notNull = true)
    private String username;
}

annotation:

@Target(value={ElementType.PARAMETER,ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckParam {
    boolean notNull()  default false;
}

aspect:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;


@Component
@Aspect
public class CheckParamAspect {
    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void methodPointCut() {}

    /**
     * 環(huán)繞切入方法
     **/
    @Around("methodPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature msig =  (MethodSignature) point.getSignature();
        Method method = msig.getMethod();
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        Object[] args = point.getArgs();
        for (int i = 0; i < args.length; i++) {
            Object obj = args[i];
            MethodParameter mp = new MethodParameter(method,i);
            mp.initParameterNameDiscovery(u);
            GenericTypeResolver.resolveParameterType(mp, method.getClass());//Spring處理參數(shù)
            //String paramName = mp.getParameterName();//參數(shù)名
            CheckParam anno = mp.getParameterAnnotation(CheckParam.class);//參數(shù)注解
            if(anno != null){
                check(obj);
            }
        }
        return point.proceed();

    }

    /**
     * 校驗成員變量
     **/
    private void check(Object obj) throws IllegalAccessException {
        Class clazz  = obj.getClass();
        for(Field field : clazz.getDeclaredFields()){
            CheckParam cp = field.getAnnotation(CheckParam.class);
            if(cp != null){
                 check(obj,clazz, field,cp);
            }
        }
    }

    /**
     * 取出注解,校驗變量
     **/
    private void check(Object obj, Class clazz, Field field, CheckParam cp) throws IllegalAccessException {
        if(cp.notNull()){
            field.setAccessible(true);
            Object f = field.get(obj);
            if(StringUtils.isEmpty(f)){
                throw  new IllegalArgumentException("類" + clazz.getName() + "成員" + field.getName() + "檢測到非法參數(shù)");
            }
        }
    }
}

3.自定義ValidationUtils
表單驗證工具類ValidationUtils,依賴包commons-lang

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

public class ValidateUtils {
    /**
     * @param fields
     * @param params
     * @return
     * 不存在的校驗規(guī)則:返回true
     * 關鍵字不按要求寫:返回true
     */
    public static SKResult validate(ValidField[] fields, Map<String, String> params){

        try {
            for(ValidField field : fields){
                String name = field.getName();
                String desc = field.getDes();
                boolean isValid = field.isValid();
                String[] rules = field.getRules();
                String value = params.get(name); // 對應請求參數(shù)值
                if(!isValid){
                    return new SKResult(true, "");
                }
                for(String rule : rules){
                    String[] arr = rule.replaceAll(" ", "").split(":");
                    String arr1 = arr[0]; // required
                    String arr2 = arr[1]; // true
                    switch (arr1) {
                    case "required": // 必須項 required:true|false
                        if(Boolean.parseBoolean(arr2)){
                            if(value==null || value.trim().length()==0){
                                return new SKResult(false, desc+"不能為空");
                            }
                        }
                        break;
                    case "number": // 必須輸入合法的數(shù)字(負數(shù),小數(shù)) number:true|false
                        if(Boolean.parseBoolean(arr2)){
                            try{
                                Double.valueOf(value);
                            }catch(Exception e){
                                return new SKResult(false, desc+"數(shù)值類型不合法");
                            }
                        }
                        break;
                    default:
                        break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("===ValidField格式不合法,請注意檢查!");
            return new SKResult(true, "ValidField格式不合法");
        }
        return new SKResult(true, "校驗通過");
    }

    public static void main(String[] args) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("username", "18702764599");
        params.put("password", "123");
        ValidField[] fields = {
                new ValidField("username", "手機號", true, new String[]{
                    "required:true",
                    "isTel:true"
                    "min:5"
                    "max:5"
                }),
                new ValidField("password", "密碼", true, new String[]{
                    "required:true",
                    "isPassword:true",
                    "equalTo:#username"
                    "max:2"
                })
        };

        SKResult sk = ValidateUtils.validate(fields, params);
        System.out.println(sk);
        //SKResult [result=true, respMsg=校驗通過, obj=null, type=null]
    }
}

SKResult :

public class SKResult {
    // 返回代碼
    private boolean result;
    // 錯誤信息
    private String respMsg;
    private Object obj;

    //set.get方法
    @Override
    public String toString() {
        return "SKResult [result=" + result + ", respMsg=" + respMsg + ", obj="
                + obj + ", type=" + type + "]";
    }
}

ValidField :

public class ValidField {   
    /**
     * 字段名
     */
    private String name;
    /**
     * 字段描述
     */
    private String des;
    /**
     * 為true必須校驗
     */
    private boolean isValid = false; 
    /**
     * 校驗規(guī)則
     */
    private String[] rules;

    public String[] getRules() {
        return rules;
    }
    public void setRules(String[] rules) {
        this.rules = rules;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDes() {
        return des;
    }
    public void setDes(String des) {
        this.des = des;
    }
    public boolean isValid() {
        return isValid;
    }
    public void setValid(boolean isValid) {
        this.isValid = isValid;
    }
    public ValidField(String name, String des, boolean isValid, String[] rules) {
        super();
        this.name = name;
        this.des = des;
        this.isValid = isValid;
        this.rules = rules;
    }
}
  1. JSR-303規(guī)范,Bean Validation
    JSR 303(Java Specification Requests 規(guī)范提案)是JAVA EE 6中的一項子規(guī)范,一套JavaBean參數(shù)校驗的標準,叫做Bean Validation。JSR 303用于對Java Bean中的字段的值進行驗證,Spring MVC 3.x之中也大力支持 JSR-303,可以在控制器中對表單提交的數(shù)據(jù)方便地驗證。
       <!--jsr 303-->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
        <!-- hibernate validator-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.2.0.Final</version>
        </dependency>
package com.example.demo;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.util.Iterator;
import java.util.Set;

/**
 * @author lanxinghua
 * @date 2018/08/05 15:51
 * @description
 */
public class ValidateTestClass {
    @NotNull(message = "reason信息不可以為空")
    @Pattern(regexp = "[1-7]{1}", message = "reason的類型值為1-7中的一個類型")
    private String reason;

    public void setReason(String reason) {
        this.reason = reason;
    }

    public void validateParams() {
        //調(diào)用JSR303驗證工具,校驗參數(shù)
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<ValidateTestClass>> violations = validator.validate(this);
        Iterator<ConstraintViolation<ValidateTestClass>> iter = violations.iterator();
        if (iter.hasNext()) {
            String errMessage = iter.next().getMessage();
            throw new ValidationException(errMessage);
        }
    }
}
package com.example.demo;

/**
 * @author lanxinghua
 * @date 2018/08/05 15:56
 * @description
 */
public class ValidateTestClassValidateTest {
    public static void main(String[] args) {
        ValidateTestClass validateTestClass = new ValidateTestClass();
        validateTestClass .setReason(null);
        validateTestClass .validateParams(); //調(diào)用驗證的方法
    }
}
  1. JSR-303規(guī)范,Bean Validation在SSM項目中使用
    JSR和Hibernate validator的校驗只能對Object的屬性進行校驗。
    5.1 Model 中添加校驗注解
public class Book {
   private long id;
   @NotEmpty(message = "書名不能為空")
   private String bookName;
   @NotNull(message = "ISBN號不能為空")
   private String bookIsbn;
   @DecimalMin(value = "0.1",message = "單價最低為0.1")
private doubleprice; // getter setter .......  }

5.2 在controller中使用此校驗

@RequestMapping(value = "/book",method = RequestMethod.POST)
   public void addBook(@RequestBody @Valid Book book) {
       System.out.println(book.toString());
}

5.3 分組驗證
對同一個Model,我們在增加和修改時對參數(shù)的校驗也是不一樣的,這個時候我們就需要定義分組驗證,步驟如下:

定義兩個空接口,分別代表Person對象的增加校驗規(guī)則和修改校驗規(guī)則

//可以在一個Model上面添加多套參數(shù)驗證規(guī)則,此接口定義添加Person模型修改時的參數(shù)校驗規(guī)則

public interface PersonAddView {}

public interface PersonModifyView {}

Model上添加注解時使用指明所述的分組

public class Person {
   private long id;
   /**
    * 添加groups 屬性,說明只在特定的驗證規(guī)則里面起作用,不加則表示在使用Deafault規(guī)則時起作用
    */

   @NotNull(groups = {PersonAddView.class, PersonModifyView.class}, message= "添加、修改用戶時名字不能為空",payload = ValidateErrorLevel.Info.class)

   @ListNotHasNull.List({
           @ListNotHasNull(groups = {PersonAddView.class}, message = "添加上Name不能為空"),
           @ListNotHasNull(groups = {PersonModifyView.class}, message = "修改時Name不能為空")})
   private String name;

   @NotNull(groups = {PersonAddView.class}, message = "添加用戶時地址不能為空")
   private String address;

   @Min(value = 18, groups = {PersonAddView.class}, message = "姓名不能低于18歲")
   @Max(value = 30, groups = {PersonModifyView.class}, message = "姓名不能超過30歲")
   private int age;
 //getter setter 方法......

}

此時啟用校驗和之前的不同,需要指明啟用哪一組規(guī)則

/**
 * 備注:此處@Validated(PersonAddView.class)表示使用PersonAndView這套校驗規(guī)則,若使用@Valid 則表示使用默認校驗規(guī)則,若兩個規(guī)則同時加上去,則只有第一套起作用
 * 修改Person對象
 * 此處啟用PersonModifyView這個驗證規(guī)則
*/
@RequestMapping(value = "/person", method = RequestMethod.PUT)
public void modifyPerson(@RequestBody @Validated(value ={PersonModifyView.class}) Person person) {
       System.out.println(person.toString());
}
  1. Spring validator 方法級別的校驗
    JSR和Hibernate validator的校驗只能對Object的屬性進行校驗,不能對單個的參數(shù)進行校驗,spring 在此基礎上進行了擴展,添加了MethodValidationPostProcessor攔截器,可以實現(xiàn)對方法參數(shù)的校驗
public @NotNull UserModel get2(@NotNull @Size(min = 1) Integer uuid) {  
    //獲取 User Model  
    UserModel user = new UserModel(); //此處應該從數(shù)據(jù)庫獲取  
    return user;  
}  
  1. java開源驗證框架OVAL
    我發(fā)現(xiàn)我們公司dubbo服務暴露的接口用這套框架來驗證。
//下單支付預處理
@Validator({@Check(name = "orderDTO", adapter = NotNull.class, message = "訂單詳情不能為空", errorCode = "10")})
Result<BossOrderDTO> orderPayPrepare(BossOrderDTO orderDTO);

hibernater-validator依賴于validation-api,說明這個框架是實現(xiàn)了bean validation規(guī)范的,從測試中也可以看出,既可以使用javax.validation包下的注解來做校驗,也可以使用自身的注解;而oval不依賴于validation-api.兩者大同小異,實現(xiàn)的原理也差不多. Java開源驗證框架Oval是一個可擴展的Java對象數(shù)據(jù)驗證框架,功能強大使用簡單,驗證規(guī)則可通過配置文件、注解等方式進行設置,規(guī)則的編寫可以使用純Java、JavaScript 、Groovy 、BeanShell等語言。

<dependency>
    <groupId>net.sf.oval</groupId>
    <artifactId>oval</artifactId>
    <version>1.81</version>
</dependency>

實現(xiàn)Oval實體對象類,用戶的年齡和名字進行校驗,具體代碼如下:

public class OvalTest {
    @Min(18)
    private int age;
    @Length(min = 6, max = 12)
    private String name;

    public static void main(String[] args) {
        OvalTest ovalTest = new OvalTest();
        ovalTest.age = 12;
        ovalTest.name = "yoodb";
        Validator validator = new Validator(); 
        List<ConstraintViolation> ret = validator.validate(ovalTest);
        System.out.println(ret);
    }
}

JSR提供的校驗注解:

@Null 被注釋的元素必須為 null
@NotNull 被注釋的元素必須不為 null
@AssertTrue 被注釋的元素必須為 true
@AssertFalse 被注釋的元素必須為 false
@Min(value) 被注釋的元素必須是一個數(shù)字,其值必須大于等于指定的最小值
@Max(value) 被注釋的元素必須是一個數(shù)字,其值必須小于等于指定的最大值
@DecimalMin(value) 被注釋的元素必須是一個數(shù)字,其值必須大于等于指定的最小值
@DecimalMax(value) 被注釋的元素必須是一個數(shù)字,其值必須小于等于指定的最大值
@Size(max=, min=) 被注釋的元素的大小必須在指定的范圍內(nèi)
@Digits (integer, fraction) 被注釋的元素必須是一個數(shù)字,其值必須在可接受的范圍內(nèi)
@Past 被注釋的元素必須是一個過去的日期
@Future 被注釋的元素必須是一個將來的日期
@Pattern(regex=,flag=) 被注釋的元素必須符合指定的正則表達式

Hibernate Validator提供的校驗注解:

@NotBlank(message =) 驗證字符串非null,且長度必須大于0
@Email 被注釋的元素必須是電子郵箱地址
@Length(min=,max=) 被注釋的字符串的大小必須在指定的范圍內(nèi)
@NotEmpty 被注釋的字符串的必須非空
@Range(min=,max=,message=) 被注釋的元素必須在合適的范圍內(nèi)

7.捕獲異常

@Slf4j
@ControllerAdvice
@RestControllerAdvice
public class MyExceptionHander {

    /**
     * Bean Validation參數(shù)校驗異常處理器
     * @param e 參數(shù)驗證異常
     * @return ResultObject
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public JsonResult parameterExceptionHandler(MethodArgumentNotValidException e) {
        int errCode = 1006;
        // 獲取異常信息
        BindingResult exceptions = e.getBindingResult();
        // 這里列出了全部錯誤參數(shù),這里用List傳回
        List<ObjectError> errors = exceptions.getAllErrors();
        // 判斷異常中是否有錯誤信息,如果存在就使用異常中的消息,否則使用默認消息
        if(ObjectUtils.isNotEmpty(errors)){
            return new JsonResult(errCode, errors.get(0).getDefaultMessage());
        }
        return new JsonResult(errCode,"請求參數(shù)校驗錯誤");
    }

}

————————————————
版權聲明:本文為CSDN博主「藍星花」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權協(xié)議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/m0_37499059/java/article/details/81431562

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容