1. 程式人生 > >規範-前、後臺請求引數校驗

規範-前、後臺請求引數校驗

1. 什麼時候我們會前、後端校驗?

正常情況下,前後端對於請求的引數都需要校驗的,這能提高應用程式的穩定性、可維護性,而對於前後臺如果能將這種不可缺少校驗規則彙總並制定一套規範,在每一個應用程式中都使用這種規範,能給帶來不少好處。那在哪些情況下適合使用前、後端校驗了:

  • 應用程式業務單一、後期維護少、不涉及敏感資訊,如:公司內部OA系統,這種系統可以直接使用前端校驗,而這裡的前端引數校驗可以使用:H5表單校驗或者封裝常用校驗JS檔案。
  • 應用程式業務單一、後期維護少;如:支付系統,由於支付系統可能會有其他公司對接平臺的介面,所有這種前端校驗就交給其他公司了,我們只需要做好後端校驗就行。
  • 業務複雜、後期維護多、安全可用性要求高,如:電商專案的維護,這種方式要同時使用前後端校驗,前端校驗的目的是為了把更多的錯誤請求都在瀏覽器層面就已經攔截處理,不會消耗服務端的記憶體和執行緒數,可以提供效能;對於還要進行後端校驗是為了提高系統的穩定性,不要動不動就500,還能防止一些人惡意攻擊網站等等。

2. 前端請求引數校驗

常用的方式有這些:

  • 自己封裝一個通用校驗JS檔案,統一校驗方式(使用與JS傳送請求)
  • H5標籤屬性檢驗方式(適用於web form表單提交)
  • 第三方JS自己封裝的校驗方法,這裡對前端的建議儘量統一起來、規範起來。

3. 後端請求引數校驗

常用的方式有這些:

  • 不校驗,我對比了之前開發的一些小系統(外包)對於後端引數基本沒有,這種方式的確可以做到後端開發快,所有的校驗都交給前端做,但對於前端不友好,如:由於前端少傳遞一個引數,導致後端程式報錯,而後端又沒有提供詳細的報錯資訊,這給前端對接帶來了問題,前端不知道自己錯在哪裡,這個時候可能還的和後端人員進行溝通,後端看看Log再告訴前端,這種方式對於前端對接不友好並且效率低。
  • 封裝自己的校驗工具類進行檢驗,這種方式的確能做到後端交易,但如果需要校驗的引數比較多對程式是不友好的,如:

  • 使用@RequestParam註解完成簡單非空校驗,這種雖然可以檢驗,但如果沒有傳此欄位會丟擲異常,這裡需要通過全域性異常捕獲統一處理。
@RequestParam(value = "mobile", required = true) String mobile
  • 使用Interceptor、Filter、Aop.. 做公共部分的業務做統一的校驗處理,如:Token檢驗,許可權校驗..
  • 如果需要校驗的引數比較多,校驗方式和業務程式碼混合在一塊不方便於程式碼的維護,可以使用hibernate-validator來做分組校驗。

雖然到這裡通過hibernate-validator來做分組校驗就可以解決所有方式的引數校驗:

但也存在問題,後端校驗的確做到了,但如果要將這些引數校驗都編寫到介面文件中,這個時候我們還需要先找介面、找到分組、找到dto下分組對應的所有引數校驗,增加引數校驗規則還得重新修改介面文件等等。

4. 後端引數校驗總結

目前後端校驗基本就是上面我提到的幾種常用方式,但這些方式都有缺點,基本上hibernate-validator已經算是比較好的了,所以這裡推薦使用(適用於大部分專案),使用hibernate-validator也存在問題,就是介面文件編寫,這裡引入一個介面管理框架swagger,swagger可以統一管理api並將api提供給前端人員,swagger目前可以做到通過編寫yaml檔案,根據yaml中的引數必填的屬性配置,可以通過yaml生成對應的介面程式碼且介面程式碼中已經做了引數校驗,以後對於引數校驗可以直接修改yaml檔案並重新生成就行了,同時yaml還可以直接提供給前端人員做mock或生成介面文件。對於yaml生成後端程式碼,我會在後面的部落格繼續提到,這裡只簡單提到對於hibernate-validator文件管理痛點引入的swagger yaml生成後端程式碼。

基於yaml生成的後端程式碼:

    public ResponseEntity<ApiCommonResultVo> loginUsingPOST(@NotNull @ApiParam(value = "賬號", required = true) @Valid @RequestParam(value = "mobile", required = true) String mobile,@NotNull @ApiParam(value = "密碼", required = true) @Valid @RequestParam(value = "password", required = true) String password) {
        String accept = request.getHeader("Accept");
        if (accept != null && accept.contains("")) {
            try {
                return new ResponseEntity<ApiCommonResultVo>(objectMapper.readValue("", ApiCommonResultVo.class), HttpStatus.NOT_IMPLEMENTED);
            } catch (IOException e) {
                log.error("Couldn't serialize response for content type ", e);
                return new ResponseEntity<ApiCommonResultVo>(HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }
        log.info("login success...");
        return new ResponseEntity<ApiCommonResultVo>(HttpStatus.NOT_IMPLEMENTED);
    }