哦哇資訊網

立體式校驗保護,讓你的系統避免 90% 以上的 bug

由 後端技術分享 發表于 美食2022-11-30

1。 概覽

在實際開發過程中,資料校驗是最為重要的一環,問題資料一旦進入系統,將對系統造成不可估量的損失。輕者,查詢時觸發空指標異常,導致整個頁面不可用;重者,業務邏輯錯誤,造成流量甚至金錢上的損失。

1。1。 背景

資料校驗,天天都在做,但因此而引起的bug也一直沒有中斷。沒有經驗的同學精力只定在正常流程,對於邊界條件視而不見;有經驗的同學,編寫大量的程式碼,對資料進行驗證,確實大幅提升了系統的健壯性,但也耗費了大量精力。

對此,我們需要:

一套完整方法論和工具,對系統進行立體式防護;

簡單快捷,快速接入,降低開發負擔;

1。2。 目標

首先,先看下應用程式架構,其中的每一個層次都需不同的驗證機制進行保障。

常用應用架構

構建完整的驗證體系,從各個層次對應用服務提供保護,需考慮:

應用層引數驗證,包括:

入參驗證

嵌入物件驗證

自定義邏輯驗證

領域層業務驗證。

業務規則外掛化

儲存層規則驗證;

入庫前規則校驗

2。 快速入門

2。1。 Spring Validator 入門

Spring 對 Validator 提供了支援,可以對簡單屬性進行驗證,大大降低編碼量。

新增 vlidator starter 依賴,具體如下:

    org。springframework。boot    spring-boot-starter-validation

Starter 會自動引入 hibernate-validator,並完成與 Spring MVC 和 Spring AOP 的整合。此時,便可以使用驗證註解對入參或屬性進行標註,Bean Validation 內建的註解如下:

註解

含義

@Valid

標記的元素為一個物件,對其所有欄位進行檢測

@Null

被標註的元素必須為 null

@NotNull

被標註的元素必須不為 null

@AssertTrue

被標記的元素必須為 true

@AssertFalse

被標記的元素必須為 false

@Min(value)

被標記的元素為數值,並且大於等於最小值

@Max(value)

被標記的元素為數值,並且小於等於最大值

@DecimalMin(value)

被標記的元素為數值,並且大於等於最小值

@DecimalMax(value)

被標記的元素為數值,並且小於等於最大值

@Size(max, min)

被標記的元素必須指定範圍內

@Digits (integer, fraction)

被註釋的元素必須是一個數字,其值必須在可接受的範圍內

@Past

被註釋的元素必須是一個過去的日期

@Future

被註釋的元素必須是一個將來的日期

@Pattern(value)

被註釋的元素必須符合指定的正則表示式

Hibernat Validator 擴充套件註解如下:

註解

含義

@Email

被標註的元素必須是郵箱

@Length(min=, max=)

被標註的字串必須在指定範圍內

@NotEmpty

被標註的字串不能為空串

@Range(min=, max=)

被標註的元素必須在指定範圍內

@NotBlank

被標註的字串不能為空串

@URL(protocol=,host=, port=, regexp=, flags=)

被標記的元素必須為有效的 url

@CreditCardNumber

被註釋的字串必須透過Luhn校驗演算法,銀行卡,信用卡等號碼一般都用Luhn計算合法性

@ScriptAssert(lang=, script=, alias=)

要有Java Scripting API 即JSR 223 的實現

@SafeHtml(whitelistType=, additionalTags=)

classpath中要有jsoup包

2。2。 基礎引數驗證

基礎引數驗證是最簡單的驗證,直接使用 validator 提供的註解便可完成驗證。

2。2。1。 開啟驗證 AOP

在介面或實現類上新增 @Validated 註解,將啟動 MethodValidationInterceptor 對方法進行驗證攔截。

具體程式碼如下:

@Validatedpublic interface ApplicationValidateService {}

建議將 @Validated 註解新增到介面上,其所有實現類都會開啟方法驗證。

2。2。2。 簡單型別入參驗證

簡單型別是最常見的入參,如需對其進行驗證,只需在入參上新增對應註解即可,示例如下:

void singleValidate(@NotNull(message = “id 不能為null”) Long id);

執行測試用例:

applicationValidateService。singleValidate((Long) null);

丟擲如下異常:

javax。validation。ConstraintViolationException: singleValidate。id: id 不能為null    at org。springframework。validation。beanvalidation。MethodValidationInterceptor。invoke(MethodValidationInterceptor。java:120)    at org。springframework。aop。framework。ReflectiveMethodInvocation。proceed(ReflectiveMethodInvocation。java:186)    at org。springframework。aop。framework。CglibAopProxy$CglibMethodInvocation。proceed(CglibAopProxy。java:763)

2。2。3。 物件型別入參驗證

為了方便,經常將多個屬性封裝到一個物件中,並使用該物件作為入參,如果想對物件型別的入參進行驗證需要:

在物件的屬性上根據需求增加驗證註解,示例如下:

@Datapublic class SingleForm {    @NotNull(message = “id不能為null”)    private Long id;    @NotEmpty(message = “name不能為空”)    private String name;}

在方法入參處使用 @Valid 註解,示例如下:

void singleValidate(@Valid @NotNull(message = “form 不能為 null”) SingleForm singleForm);

此時,singleValidate 便擁有:

singleForm 入參不能為空驗證

singleForm 示例屬性驗證

id 不能為null

name 不能為空

執行單元測試:

this。applicationValidateService。singleValidate((SingleForm) null);

丟擲如下異常:

javax。validation。ConstraintViolationException: singleValidate。singleForm: form 不能為 null    at org。springframework。validation。beanvalidation。MethodValidationInterceptor。invoke(MethodValidationInterceptor。java:120)    at org。springframework。aop。framework。ReflectiveMethodInvocation。proceed(ReflectiveMethodInvocation。java:186)    at org。springframework。aop。framework。CglibAopProxy$CglibMethodInvocation。proceed(CglibAopProxy。java:763)

執行單元測試:

SingleForm singleForm = new SingleForm();this。applicationValidateService。singleValidate(singleForm);

丟擲如下異常:

javax。validation。ConstraintViolationException: singleValidate。singleForm。name: name不能為空, singleValidate。singleForm。id: id不能為null    at org。springframework。validation。beanvalidation。MethodValidationInterceptor。invoke(MethodValidationInterceptor。java:120)    at org。springframework。aop。framework。ReflectiveMethodInvocation。proceed(ReflectiveMethodInvocation。java:186)    at org。springframework。aop。framework。CglibAopProxy$CglibMethodInvocation。proceed(CglibAopProxy。java:763)

2。3。 擴充套件 Validation 框架

有時僅僅驗證單個屬性無法滿足業務需求,比如在修改密碼時,需要使用者輸入兩次密碼,用以保障輸入密碼的準確性。

在這種情況下,可以對 Validation 框架進行擴充套件,具體如下:

建立一個驗證物件 Password,用於儲存兩次輸入的值,示例如下:

@Datapublic class Password {    @NotEmpty(message = “密碼不能為空”)    private String input1;    @NotEmpty(message = “確認密碼不能為空”)    private String input2;}

其中,Password 中的兩個屬性全部添加了驗證註解。

建立一個驗證元件 PasswordValidator,用於對“兩次密碼是否一致”進行驗證,示例如下:

public class PasswordValidator implements ConstraintValidator {    @Override    public boolean isValid(Password password, ConstraintValidatorContext constraintValidatorContext) {        if (password == null){            return true;        }        if (password。getInput1() == null){            return true;        }        if (password。getInput1()。equals(password。getInput2())){            return true;        }        return false;    }}

驗證元件實現 ConstraintValidator 介面,僅當兩次密碼一致時透過驗證。

建立驗證註解 PasswordConsistency,程式碼如下:

@Target({ElementType。METHOD, ElementType。FIELD, ElementType。ANNOTATION_TYPE, ElementType。CONSTRUCTOR, ElementType。PARAMETER, ElementType。TYPE_USE})@Retention(RetentionPolicy。RUNTIME)@Documented@Constraint(        validatedBy = PasswordValidator。class)public @interface PasswordConsistency {    String message() default “{javax。validation。constraints。password。consistency。message}”;    Class<?>[] groups() default {};    Class<? extends Payload>[] payload() default {};}

其中 @Constraint 用於說明該註解使用的驗證器為 PasswordValidator。

一切準備好之後,並可以使用自定義驗證元件,具體如下:

void customSingleValidate(@NotNull @Valid @PasswordConsistency(message = “兩次密碼不相同”) Password password);

其中

@NotNull 表明入參 password 不能為 null

@Valid 表明對Password 的屬性進行校驗

@PasswordConsistency 表明使用 PasswordValidator 進行驗證

執行單元測試:

this。applicationValidateService。customSingleValidate(null);

執行結果如下:

javax。validation。ConstraintViolationException: customSingleValidate。password: 不能為null    at org。springframework。validation。beanvalidation。MethodValidationInterceptor。invoke(MethodValidationInterceptor。java:120)    at org。springframework。aop。framework。ReflectiveMethodInvocation。proceed(ReflectiveMethodInvocation。java:186)    at org。springframework。aop。framework。CglibAopProxy$CglibMethodInvocation。proceed(CglibAopProxy。java:763)

執行單元測試:

Password password = new Password();this。applicationValidateService。customSingleValidate(password);

執行結果如下:

javax。validation。ConstraintViolationException: customSingleValidate。password。input1: 密碼不能為空, customSingleValidate。password。input2: 確認密碼不能為空    at org。springframework。validation。beanvalidation。MethodValidationInterceptor。invoke(MethodValidationInterceptor。java:120)    at org。springframework。aop。framework。ReflectiveMethodInvocation。proceed(ReflectiveMethodInvocation。java:186)    at org。springframework。aop。framework。CglibAopProxy$CglibMethodInvocation。proceed(CglibAopProxy。java:763)

執行單元測試:

Password password = new Password();password。setInput1(“123”);password。setInput2(“456”);this。applicationValidateService。customSingleValidate(password);

執行結果如下:

javax。validation。ConstraintViolationException: customSingleValidate。password: 兩次密碼不相同    at org。springframework。validation。beanvalidation。MethodValidationInterceptor。invoke(MethodValidationInterceptor。java:120)    at org。springframework。aop。framework。ReflectiveMethodInvocation。proceed(ReflectiveMethodInvocation。java:186)    at org。springframework。aop。framework。CglibAopProxy$CglibMethodInvocation。proceed(CglibAopProxy。java:763)

2。4。 新增 Validateable 驗證

擴充套件驗證規則非常繁瑣,一個驗證需要新建註解和驗證類,並完成兩者的配置,在實際開發中使用的頻次極低。

相反,在開發中更習慣呼叫物件上的驗證方法進行資料驗證,示例如下:

if(!createUserCommand。validate()){    throw new XXXXException();}

對於這種非常通用的解決方案,lego 提供了框架支援。

2。4。1。 引入 lego starter

在配置檔案中新增 lego starter,示例如下:

    com。geekhalo。lego    lego-starter    0。1。6-validator-SNAPSHOT

基於 Spring Boot 的自動配置機制,ValidatorAutoConfiguration 將自動新增 ValidateableMethodValidationInterceptor,對方法進行攔截,進行資料校驗。

2。4。2。 應用 Validateable

比如,使用者註冊時,系統要求密碼與使用者名稱不能相同。使用 Validateable 進行驗證具體如下:

讓物件繼承自 Validateable,並實現 validate 介面,示例程式碼如下:

@Datapublic class UserValidateForm implements Validateable {    @NotEmpty    private String name;    @NotEmpty    private String password;    @Override    public void validate(ValidateErrorHandler validateErrorHandler) {        if (getName()。equals(getPassword())){            validateErrorHandler。handleError(“user”, “1”, “使用者名稱密碼不能相同”);        }    }}

驗證方法如下:

void validateForm(@NotNull @Valid UserValidateForm userValidateForm);

執行單元測試:

this。applicationValidateService。validateForm(null);

執行結果如下:

javax。validation。ConstraintViolationException: validateForm。userValidateForm: 不能為null    at org。springframework。validation。beanvalidation。MethodValidationInterceptor。invoke(MethodValidationInterceptor。java:120)    at org。springframework。aop。framework。ReflectiveMethodInvocation。proceed(ReflectiveMethodInvocation。java:186)    at org。springframework。aop。framework。CglibAopProxy$CglibMethodInvocation。proceed(CglibAopProxy。java:763)

執行單元測試:

UserValidateForm userValidateForm = new UserValidateForm();this。applicationValidateService。validateForm(userValidateForm);

執行結果如下:

javax。validation。ConstraintViolationException: validateForm。userValidateForm。name: 不能為空, validateForm。userValidateForm。password: 不能為空    at org。springframework。validation。beanvalidation。MethodValidationInterceptor。invoke(MethodValidationInterceptor。java:120)    at org。springframework。aop。framework。ReflectiveMethodInvocation。proceed(ReflectiveMethodInvocation。java:186)    at org。springframework。aop。framework。CglibAopProxy$CglibMethodInvocation。proceed(CglibAopProxy。java:763)

執行單元測試:

UserValidateForm userValidateForm = new UserValidateForm();userValidateForm。setName(“name”);userValidateForm。setPassword(“name”);this。applicationValidateService。validateForm(userValidateForm);

執行結果如下:

javax。validation。ConstraintViolationException: null: 使用者名稱密碼不能相同    at com。geekhalo。lego。starter。validator。ValidatorAutoConfiguration。lambda$validateErrorReporter$1(ValidatorAutoConfiguration。java:61)    at com。geekhalo。lego。starter。validator。ValidatorAutoConfiguration$$Lambda$749/562345204。handleErrors(Unknown Source)    at com。geekhalo。lego。core。validator。ValidateableMethodValidationInterceptor。invoke(ValidateableMethodValidationInterceptor。java:39)

2。5。 業務規則外掛化

在一些複雜流程中,業務規則校驗邏輯佔比非常重,大量的 if-else 充斥在主流程中非常不便於維護。

在這種場景下,建議將驗證元件外掛化,使得每個驗證邏輯全部封裝在一個類中,將邏輯進行拆分,最終實現“開閉原則”。

2。5。1。 初識 ValidateService

ValidateService 整體架構如下:

image

其中,包括兩個核心元件:

1。BeanValidator。業務驗證介面,由開發人員實現,用於承載驗證邏輯,包括:

support 方法(繼承自SmartComponent)用於定義元件應用場景

validate 方法,實現業務邏輯

2。ValidateService。驗證服務的入口,主要職責包括:

管理所有的 BeanValidator 實現,由 Spring 完成所有的 BeanValidator 例項注入,並對其進行統一管理;

對外提供 validate 方法,從 BeanValidator 例項中選擇對應的元件,並呼叫 BeanValidator 的 validate 方法;

整體介紹完成後,讓我們看一個真實案例。比如,在一個生單流程中,我們需要保障:

使用者必須存在,並且為可用狀態;

商品必須存在,並且為售賣狀態;

庫存餘量必須大於購買數量;

這三個規則相互獨立,沒有太多關聯關係,如果在一個方法中編寫,便會產生強耦合,不利於應對未來的變更。這種情況下,最佳方案是將其封裝到不同的元件中。示例如下:

UserStatusValidator

@Order(1)@Componentpublic class UserStatusValidator        extends FixTypeBeanValidator {    @Override    public void validate(CreateOrderContext context, ValidateErrorHandler validateErrorHandler) {        if (context。getUser() == null){            validateErrorHandler。handleError(“user”, “1”, “使用者不存在”);        }        if (!context。getUser()。isEnable()){            validateErrorHandler。handleError(“user”, “2”, “當前使用者不可以”);        }    }}

ProductStatusValidator

@Component@Order(2)public class ProductStatusValidator        extends FixTypeBeanValidator {    @Override    public void validate(CreateOrderContext context, ValidateErrorHandler validateErrorHandler) {        if(context。getProduct() == null){            validateErrorHandler。handleError(“product”, “2”, “商品不存在”);        }        if (!context。getProduct()。isSaleable()){            validateErrorHandler。handleError(“product”, “3”, “商品不可售賣”);        }    }}

StockCapacityValidator

@Component@Order(3)public class StockCapacityValidator        extends FixTypeBeanValidator {    @Override    public void validate(CreateOrderContext context, ValidateErrorHandler validateErrorHandler) {        if (context。getStock() == null){            validateErrorHandler。handleError(“stock”, “3”, “庫存不存在”);        }        if (context。getStock()。getCount() < context。getCount()){            validateErrorHandler。handleError(“stock”, “4”, “庫存不足”);        }    }}

三個驗證元件具有以下特徵:

繼承自 FixTypeBeanValidator,僅對 CreateOrderContext 進行處理

使用 @Component 將其宣告為 Spring 的託管bean,從而被框架所感知;

使用 @Order(n) 標記執行順序

其中,FixTypeBeanValidator 會根據泛型進行型別判斷,自動完成元件的篩選。程式碼如下:

public abstract class FixTypeBeanValidator implements BeanValidator{    private final Class type;    protected FixTypeBeanValidator(){        Class type = (Class)((ParameterizedType)getClass()                。getGenericSuperclass())                。getActualTypeArguments()[0];        this。type = type;    }    protected FixTypeBeanValidator(Class type) {        this。type = type;    }    @Override    public final boolean support(Object a) {        return this。type。isInstance(a);    }}

有了驗證元件後,可以直接使用 ValidateService 進行驗證,具體示例程式碼如下:

@Overridepublic void createOrder(CreateOrderContext context) {    validateService。validate(context);}

執行測試用例:

CreateOrderContext context = new CreateOrderContext();context。setUser(User。builder()        。build());context。setProduct(Product。builder()        。build());context。setStock(Stock。builder()        。count(0)        。build());context。setCount(1);this。domainValidateService。createOrder(context);

執行結果如下:

ValidateException(name=stock, code=4, msg=庫存不足)    at com。geekhalo。lego。core。validator。BeanValidator。lambda$validate$0(BeanValidator。java:17)    at com。geekhalo。lego。core。validator。BeanValidator$$Lambda$1383/1570024586。handleError(Unknown Source)    at com。geekhalo。lego。validator。StockValidator。validate(StockValidator。java:24)    at com。geekhalo。lego。validator。StockValidator。validate(StockValidator。java:13)

該設計符合開閉原則:

新增驗證規則時,只需編寫新的驗證元件;

修改驗證規則時,只需修改對應的驗證元件,其他邏輯不受影響;

2。5。2。 與 LazyLoad 整合

有了靈活的驗證體系,最麻煩的就是對 Context 的維護,主要矛盾為:

如果一次性載入 Context 的全部資料,可能在第一個驗證元件就中斷流程,白白載入了過多資料;

可以在獲取的時候進行判斷,只有為 null 的時候才進行載入。但,如果多個元件依賴同一組資料,將會:

每個元件都需要寫一遍載入邏輯

為了避免多次載入,需要將資料寫回到 Context 例項

載入邏輯和驗證邏輯放在一起,職責混亂

對於這種情況,最好的方式便是讓 Context 具有延時載入的能力,其特徵如下:

只有在呼叫 getter 方法時,才觸發載入,避免全部載入產生的浪費

成功載入後,將資料透過 setter 寫回到 Context,由其他元件進行共享

這正是 LazyLoad 的設計初衷,示例如下:

定義一個具有延時載入能力的 Context,程式碼如下:

@Datapublic class CreateOrderContextV2 implements CreateOrderContext{    private CreateOrderCmd cmd;    @LazyLoadBy(“#{@userRepository。getById(cmd。userId)}”)    private User user;    @LazyLoadBy(“#{@productRepository。getById(cmd。productId)}”)    private Product product;    @LazyLoadBy(“#{@addressRepository。getDefaultAddressByUserId(user。id)}”)    private Address defAddress;    @LazyLoadBy(“#{@stockRepository。getByProductId(product。id)}”)    private Stock stock;    @LazyLoadBy(“#{@priceService。getByUserAndProduct(user。id, product。id)}”)    private Price price;}

基於 CreateOrderContextV2 編寫驗證元件,程式碼如下:

@Component@Order(3)public class StockCapacityV2Validator        extends FixTypeBeanValidator {    @Override    public void validate(CreateOrderContextV2 context, ValidateErrorHandler validateErrorHandler) {        if (context。getStock() == null){            validateErrorHandler。handleError(“stock”, “3”, “庫存不存在”);        }        if (context。getStock()。getCount() < context。getCmd()。getCount()){            validateErrorHandler。handleError(“stock”, “4”, “庫存不足”);        }    }}

編寫驗證服務,程式碼如下:

@Overridepublic void createOrder(CreateOrderCmd cmd) {    CreateOrderContextV2 context = new CreateOrderContextV2();    context。setCmd(cmd);    CreateOrderContextV2 contextProxy = this。lazyLoadProxyFactory。createProxyFor(context);    this。validateService。validate(contextProxy);}

lazyLoadProxyFactory 生成具有延遲載入能力的 Context 物件。

執行單元測試,核心程式碼如下:

CreateOrderCmd cmd = new CreateOrderCmd();cmd。setCount(10000);cmd。setProductId(100L);cmd。setUserId(100L);this。domainValidateService。createOrder(cmd);

執行結果如下:

ValidateException(name=stock, code=4, msg=庫存不足)    at com。geekhalo。lego。core。validator。BeanValidator。lambda$validate$0(BeanValidator。java:17)    at com。geekhalo。lego。core。validator。BeanValidator$$Lambda$1388/1691696909。handleError(Unknown Source)    at com。geekhalo。lego。validator。StockCapacityV2Validator。validate(StockCapacityV2Validator。java:25)    at com。geekhalo。lego。validator。StockCapacityV2Validator。validate(StockCapacityV2Validator。java:14)    at com。geekhalo。lego。core。validator。BeanValidator。validate(BeanValidator。java:16)    at com。geekhalo。lego。core。validator。ValidateService。lambda$validate$5(ValidateService。java:34)

2。6。 持久化前規則驗證

將問題資料寫入到資料庫是一個高危操作,輕則出現展示問題,比如 空指標異常;重則出現邏輯問題,比如金額對不上等。

一個最常見的例子便是 訂單系統的金額計算。隨著業務的發展,金額計算變得越來越複雜,比如優惠券、滿贈、滿減、VIP 使用者折扣等,這些業務都會對 訂單上的金額進行操作,一旦出現bug將導致嚴重的問題。

由於上層的更新入口太多,很難有一套行之有效的機制保障其不出問題。不如換個視角,在將變更同步到資料庫前,有沒有一種比較通用的檢測機制能發現金額問題?

其實是有的,無論上層業務怎麼變化,金額恆等式是不變的,及:

使用者支付金額 = 商品總售賣金額(售價 * 數量) - 優惠總金額 - 手工改價金額

只需在變更寫回資料庫前執行校驗邏輯,如果不符合公式,則直接丟擲異常。

很多框架都提供了對實體生命週期的擴充套件,比如 JPA 就提供了大量註解,以便在實體生命週期中嵌入回撥方法。

以標準的Order設計為例,具體如下:

@Entity@Table(name = “validate_order”)@Datapublic class ValidateableOrder {    @Id    @GeneratedValue(strategy = GenerationType。IDENTITY)    private Long id;    /**     * 支付金額     */    private Integer payPrice;    /**     * 售價     */    private Integer sellPrice;    /**     * 購買數量     */    private Integer amount;    /**     * 折扣價     */    private Integer discountPrice;    /**     * 手工改價     */    private Integer manualPrice;    @PrePersist    @PreUpdate    void checkPrice(){        Integer realPayPrice = sellPrice * amount - discountPrice - manualPrice;        if (realPayPrice != payPrice){            throw new ValidateException(“order”, “570”, “金額計算錯誤”);        }    }}

其中,@PrePersist 和 @PreUpdate 註解表明,checkPrice 方法在儲存前和更新前進行回撥,用以驗證是否破壞了金額計算邏輯。

使用 JpaRepository 對資料進行儲存,具體如下:

public void createOrder(ValidateableOrder order){    this。repository。save(order);}

執行單元測試,程式碼如下:

ValidateableOrder order = new ValidateableOrder();order。setSellPrice(20);order。setAmount(2);order。setDiscountPrice(5);order。setManualPrice(1);order。setPayPrice(35);this。applicationService。createOrder(order);

執行結果如下:

ValidateException(name=order, code=570, msg=金額計算錯誤)    at com。geekhalo。lego。validator。ValidateableOrder。checkPrice(ValidateableOrder。java:53)    at sun。reflect。NativeMethodAccessorImpl。invoke0(Native Method)    at sun。reflect。NativeMethodAccessorImpl。invoke(NativeMethodAccessorImpl。java:62)    at sun。reflect。DelegatingMethodAccessorImpl。invoke(DelegatingMethodAccessorImpl。java:43)    at java。lang。reflect。Method。invoke(Method。java:483)    at org。hibernate。jpa。event。internal。EntityCallback。performCallback(EntityCallback。java:50)

不僅如此,Spring 對事務進行回滾,避免髒資料進入到資料庫。

3。 小結

對應用程式提供一套立體式的驗證保障機制,包括:

應用層的基礎資料校驗

業務層的業務邏輯校驗

儲存層的持久化前校驗

這些措施共同發力,徹底將問題資料拒絕於系統之外。

4。 專案資訊

專案倉庫地址:https://gitee。com/litao851025/lego

專案文件地址:https://gitee。com/litao851025/lego/wikis/support/validator

TAG: 驗證Javaorgspringframework如下