1. 程式人生 > >後臺引數驗證的幾種方式

後臺引數驗證的幾種方式

前臺和後臺驗證(MVC、Struts2)的必要性經驗總結:

1.後端驗證是必需的,只有後端驗證才能保證表單資料輸入的合法性,前端驗證的主要目的是為了方便使用者,增強使用者體驗。
2.雖然不是必需的,但目前也算是一種發展趨勢,特別是面向一般使用者的網站,沒有加前端驗證可能會加大使用者註冊跑路率。
3.前端驗證方式:
1)目前主流的Web框架已經集成了前後端驗證功能,如:Asp.net mvc,PHP 的Yii 等,只要定好驗證規則,前端驗證程式碼就自動生成好,後端驗證也很方便。
2)前端驗證程式碼除非特殊情況或以學習練習為目的,就不要再自己一個個寫了,因為實際開發的時候是根本沒時間讓你慢慢自個去寫的。
3)真正工作時使用一般使用: jquery.validate.js 外掛才能體會到前端驗證的酸爽,通過外掛可以使用一些自帶的驗證方式,也可以自定義驗證規則
4.如果還不會使用jquery.validate.js可以去看我部落格的另一篇文章《jquery.validate.js的使用教程》,一定會讓你受益匪淺的
---------------------

後端校驗比前端校驗更安全,更可靠,前端校驗可以增加使用者體驗,一般來說,在前端校驗的東西在後端也必須校驗(比如登陸使用者名稱、密碼),有些東西在前端就可以校驗,比如:字串長度、郵箱格式、手機號碼等等,沒必要提交到後端,增加伺服器的壓力,正常情況下,前端校驗的東西,最好後端都在校驗一次。

      放到後端校驗的,常見的與資料庫有關,比如輸入重複之類的,需要先查詢資料庫才知道,當然還有其他的一些東西。

 

//java檢測是否為電話號碼(手機、固定電話驗證)

String legalPhone = "";
String regExp ="^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}|[0]{1}[0-9]{2,3}-[0-9]{7,8}$";
Pattern p = Pattern.compile(regExp);
Matcher m = p.matcher(importPotentialBFOs[i].getLegalPhone());
if(m.find()){ //注意:m.find只能用一次,第二次呼叫後都為false
 legalPhone = importPotentialBFOs[i].getLegalPhone();
 uploadTmp.setLegalTelephone(legalPhone);

}else{
 throw new BizException("聯絡電話格式錯誤!");
}
---------------------

引數驗證是一個常見的問題,無論是前端還是後臺,都需對使用者輸入進行驗證,以此來保證系統資料的正確性。對於web來說,有些人可能理所當然的想在前端驗證就行了,但這樣是非常錯誤的做法,前端程式碼對於使用者來說是透明的,稍微有點技術的人就可以繞過這個驗證,直接提交資料到後臺。無論是前端網頁提交的介面,還是提供給外部的介面,引數驗證隨處可見,也是必不可少的。總之,一切使用者的輸入都是不可信的。

  引數驗證有許多種方式進行,下面以mvc為例,列舉幾種常見的驗證方式,假設有一個使用者註冊方法

  [HttpPost]
  public ActionResult Register(RegisterInfo info)

一、通過 if-if 判斷  

1 2 3 4 5 6 7 8 if ( string .IsNullOrEmpty(info.UserName)) {      return  FailJson( "使用者名稱不能為空" ); } if ( string .IsNullOrEmpty(info.Password)) {      return  FailJson( "使用者密碼不能為空" ) }

  逐個對引數進行驗證,這種方式最粗暴,但當時在WebForm下也確實這麼用過。對於引數少的方法還好,如果引數一多,就要寫n多的if-if,相當繁瑣,更重要的是這部分判斷沒法重用,另一個方法又是這樣判斷。

二、通過 DataAnnotation

  mvc提供了DataAnnotation對Action的Model進行驗證,說到底DataAnnotation就是一系列繼承了ValidationAttribute的特性,例如RangeAttribute,RequiredAttribute等等。ValidationAttribute 的虛方法IsValid 就是用來判斷被標記的物件是否符合當前規則。asp.net mvc在進行model binding的時候,會通過反射,獲取標記的ValidationAttribute,然後呼叫 IsValid 來判斷當前引數是否符合規則,如果驗證不通過,還會收集錯誤資訊,這也是為什麼我們可以在Action裡通過ModelState.IsValid判斷Model驗證是否通過,通過ModelState來獲取驗證失敗資訊的原因。例如上面的例子:

1 2 3 4 5 6 7 8 public  class  RegisterInfo {      [Required(ErrorMessage= "使用者名稱不能為空" )]      public  string  UserName{ get ; set ;}        [Required(ErrorMessage= "密碼不能為空" )]      public  string  Password {  get set ; } }

  事實上在webform上也可以參照mvc的實現原理實現這個過程。這種方式的優點的實現起來非常優雅,而且靈活,如果有多個Action共用一個Model引數的話,只要在一個地方寫就夠了,關鍵是它讓我們的程式碼看起來非常簡潔。

  不過這種方式也有缺點,通常我們的專案可能會有很多的介面,比如幾十個介面,有些介面只有兩三個引數,為每個介面定義一個類包裝引數有點奢侈,而且實際上為這個類命名也是非常頭疼的一件事。

三、DataAnnotation 也可以標記在引數上

  通過驗證特性的AttributeUsage可以看到,它不只可以標記在屬性和欄位上,也可以標記在引數上。也就是說,我們也可以這樣寫:

1 public  ActionResult Register([Required(ErrorMessage= "使用者名稱不能為空" )] string  userName, [Required(ErrorMessage= "密碼不能為空" )] string  password)

  這樣寫也是ok的,不過很明顯,這樣寫很方法引數會難看,特別是在有多個引數,或者引數有多種驗證規則的時候。

四、自定義ValidateAttribute

  我們知道可以利用過濾器在mvc的Action執行前做一些處理,例如身份驗證,授權處理的。同理,這裡也可以用來對引數進行驗證。FilterAttribute是一個常見的過濾器,它允許我們在Action執行前後做一些操作,這裡我們要做的就是在Action前驗證引數,如果驗證不通過,就不再執行下去了。

  定義一個BaseValidateAttribute基類如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  class  BaseValidateAttribute : FilterAttribute {      protected  virtual  void  HandleError(ActionExecutingContext context)      {          for  ( int  i = ValidateHandlerProviders.Handlers.Count; i > 0; i--)          {              ValidateHandlerProviders.Handlers[i - 1].Handle(context);              if  (context.Result !=  null )              {                  break ;              }          }      } }

  HandleError 用於在驗證失敗時處理結果,這裡ValidateHandlerProviders提過IValidateHandler用於處理結果,它可以在外部進行註冊。IValidateHandler定義如下:

1 2 3 4 public  interface  IValidateHandler {      void  Handle(ActionExecutingContext context); }

  ValidateHandlerProviders定義如下,它有一個預設的處理器。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public  class  ValidateHandlerProviders {      public  static  List<IValidateHandler> Handlers {  get private  set ; }        static  ValidateHandlerProviders()      {          Handlers =  new  List<IValidateHandler>()          {              new  DefaultValidateHandler()          };      }        public  static  void  Register(IValidateHandler handler)      {          Handlers.Add(handler);      } }  

  這樣做的目的是,由於我們可能有很多具體的ValidateAttribute,可以把這模組獨立開來,而把最終的處理過程交給外部決定,例如我們在專案中可以定義一個處理器:

1 2 3 4 5 6 7 8 9 10 public  class  StanderValidateHandler : IValidateHandler {      public  void  Handle(ActionExecutingContext filterContext)      {          filterContext.Result =  new  StanderJsonResult()          {              Result = FastStatnderResult.Fail( "引數驗證失敗" , 555)          };      } }

  然後再應用程式啟動時註冊:ValidateHandlerProviders.Handlers.Add(new StanderValidateHandler());

  ValidateNullttribute:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public  class  ValidateNullAttribute : BaseValidateAttribute, IActionFilter {      public  bool  ValidateEmpty {  get set ; }        public  string  Parameter {  get set ; }        public  ValidateNullAttribute( string  parameter,  bool  validateEmpty =  false )      {          ValidateEmpty = validateEmpty;          Parameter = parameter;      }        public  void  OnActionExecuting(ActionExecutingContext filterContext)      {          string [] validates = Parameter.Split( ',' );          foreach  ( var  in  validates)          {              string  value = filterContext.HttpContext.Request[p];              if (ValidateEmpty)              {                  if  ( string .IsNullOrEmpty(value))                  {                      base .HandleError(filterContext);                  }              }              else              {                  if  (value ==  null )                  {                      base .HandleError(filterContext);                  }              }          }      }        public  void  OnActionExecuted(ActionExecutedContext filterContext)      {        } }

  ValidateRegexAttribute:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public  class  ValidateRegexAttribute : BaseValidateAttribute, IActionFilter {      private  Regex _regex;        public  string  Pattern {  get set ; }        public  string  Parameter {  get set ; }        public  ValidateRegexAttribute( string  parameter,  string  pattern)      {          _regex =  new  Regex(pattern);          Parameter = parameter;      }        public  void  OnActionExecuting(ActionExecutingContext filterContext)      {          string [] validates = Parameter.Split( ',' );          foreach  ( var  in  validates)          {              string  value = filterContext.HttpContext.Request[p];              if  (!_regex.IsMatch(value))              {                  base .HandleError(filterContext);              }          }      }        public  void  OnActionExecuted(ActionExecutedContext filterContext)      {        } }

  更多的驗證同理實現即可。

  這樣,我們上面的寫法就變成:

1 2 [ValidateNull( "userName,password" )] public  ActionResult Register( string  userName,  string  password)

  綜合看起來,還是ok的,與上面的DataAnnotation可以權衡選擇使用,這裡我們可以擴充套件更多有用的資訊,如錯誤描述等等。

總結

  當然每種方式都有有缺點,這個是視具體情況選擇了。一般引數太多建議就用一個物件包裝了。