從空值攔截到數據庫查重,五層校驗架構揭示了數據過濾的黃金法則——成本越低的檢查越應前置。本文將拆解這套源于數據庫優化思想的校驗范式,揭示前端與后端校驗的協同策略,以及如何用一張矩陣表徹底解決開發與測試的溝通難題。
———— / BEGIN / ————
上周review代碼,后端同事寫了個新增數據的接口。我點開一看,校驗邏輯大概是這么個畫風:
先查數據庫看有沒有重復記錄,再校驗字段格式,最后才判斷必填項有沒有傳。
我說兄弟,用戶連商品名都沒填呢,你就去查庫了?數據庫它也是有感情的好吧。
他愣了一下:”這有啥區別嗎?反正最后都會報錯。”
區別大了。校驗能不能攔住臟數據是及格線,校驗的順序才是你設計水平的分界線。
這事讓我想起之前踩過的一個坑。
我們系統有個”新增周度數據”的表單——商品、企業、省份、周產量、周庫存、價格、成本、毛利,十幾個字段。剛上線那會兒,校驗邏輯是前端同事”憑感覺”寫的,想到哪驗到哪。
結果呢,用戶填了個負數的價格,系統沒攔住,直接跑去做毛利計算了。算出來一個離譜的毛利率,存進了數據庫。下游的價格分析模塊一讀這條數據——Loss預測直接飛了,分析師第二天跑來問我們”這個品種是不是出bug了”。
查了半天,就是因為范圍校驗被放在了關聯計算的后面。價格是負數這件事,本來在第三層就該攔住的,結果漏到了第四層,還引發了連鎖反應。
一個校驗放錯位置,鏈路上所有下游都跟著遭殃。
這事之后我就開始琢磨:表單校驗這東西,到底有沒有一套通用的、穩定的設計范式?
還真有。而且特別樸素。
五層過濾,越便宜的檢查越先跑
我后來把校驗邏輯梳理成了五層。不是我發明的,你去看任何一個成熟框架的參數校驗,底層邏輯都是這個:
L1 — 存在性:傳沒傳?
最便宜的檢查。字段是不是null、空字符串、undefined。帶星號的必填項,這一層全覆蓋。用戶點提交,先掃一遍,沒填的直接標紅,后面全部跳過。
為什么放第一層?因為一個空值,你去做格式校驗沒意義,做范圍校驗更沒意義。就好比你拿到一個null去調.length(),不是校驗失敗的問題,是直接NPE給你看。
L2 — 格式類型:像不像?
字段有值了,看看這個值的”形狀”對不對。周產量填了”哈哈哈”——類型不對。日期字段收到一個”2026-13-45″——格式非法。郵箱沒有@符號。手機號混進了字母。
這一層本質上是做類型轉換前的門衛。過了這關,后面的邏輯才能拿到一個”至少類型是對的”的值去做進一步判斷。
很多前端框架(Ant Design的Form、Element的el-form)自帶的validator其實就管到這一層。但光靠這層遠遠不夠。
L3 — 范圍邊界:合不合理?
格式對了不代表值是合理的。價格不能為負數。庫存不能是-500萬噸。年度不能填2099年。百分比字段不能出現200%。
這一層過濾的是”格式正確但業務上離譜”的數據。我管這叫”合法的垃圾”——類型系統認它,業務邏輯不認它。
經常被忽略的一個細節:小數精度也屬于這一層。價格保留兩位小數,你傳進來一個3.14159,后面計算會不會出精度漂移?該在這里就truncate或者round掉。
L4 — 關聯邏輯:字段之間自洽嗎?
單字段都合法了,但字段之間可能打架。
毛利 = (價格 – 成本) / 價格。這三個字段之間有硬約束。用戶手動填了毛利和價格,但填的成本算出來對不上——這就是跨字段邏輯校驗該干的活。
還有一類更隱蔽的:條件必填。比如選了某個商品工藝之后,產量單位的可選范圍要聯動變化。選了”省份”之后,”企業”的下拉列表要跟著過濾。
這一層的成本比前三層高不少,因為你要同時拿到多個字段的值做交叉判斷。所以它排在第四。
L5 — 全局外部:跟系統里已有的數據沖突嗎?
最貴的一層。要查庫。
同一個”商品 + 企業 + 省份 + 周度時間”的組合,不能重復錄入。這個判斷必須發請求到后端,后端去數據庫里跑一條select。網絡IO + 數據庫查詢,這是整個校驗鏈路里成本最高的操作。
所以放在最后。只有前面四層全部pass了,才值得發這一趟請求。
你想想,如果把L5放在L1前面會怎樣?用戶連必填項都沒填全,你就發了一次數據庫查詢。并發高一點,這種無效查詢能把你的DB連接池吃得干干凈凈。
本質就是個短路求值。任何一層掛了,后面不跑。這不是什么高深的設計模式,就是最樸素的成本排序——選擇性高、代價低的條件先執行。
你去看數據庫查詢優化器選執行計劃的邏輯,一模一樣的思路。MySQL決定先走哪個索引、先過濾哪個條件,背后也是這套”便宜的先來”。
這套思路不只管表單
你再想遠一點。
API接口參數校驗,是不是同一套?收到請求 → 必傳參數在不在 → 類型對不對 → 值域合不合理 → 參數間邏輯(start_date < end_date)→ 權限校驗(查Redis/查DB)。任何一個成熟的API框架,中間件鏈的排列順序就是按這個來的。
ETL數據清洗,也是。拿到一批CSV → 空行刪掉 → 格式統一(日期轉ISO、數字去逗號)→ 異常值過濾(價格為負的行剔除)→ 跨字段一致性校驗 → 跟主表去重。你要是把去重放在第一步,幾百萬條數據先全量join一遍,跑到天荒地老。
甚至代碼review的時候,你看一個PR,下意識的掃描順序也是:這個改動有沒有(L1,別是個空PR)→ 改的對不對地方(L2,文件和模塊對不對)→ 改動幅度合不合理(L3,是不是改了不該改的)→ 跟其他模塊有沒有沖突(L4)→ CI跑過沒有(L5,外部驗證)。
同一套模型,不同的皮膚。
落地的時候,一張表搞定
回到實際工作。我現在寫PRD里的表單需求,直接用一張校驗規則矩陣表跟開發對齊:
![]()
比在PRD里寫一大段”當用戶輸入價格時,系統應判斷價格是否為空,如果為空則提示……如果不為空則繼續判斷格式……”清楚十倍。開發拿到這張表,每個字段每一層該干嘛,一目了然,不用反復對齊。
測試同事也愛這張表。寫測試用例的時候,每一層就是一組case。L1的case:每個必填字段分別傳空。L2的case:每個字段分別傳非法格式。這么列下來,漏測的概率小很多。
前端校驗和后端校驗的關系
順便說一個容易吵架的點。
前端該不該做校驗?當然該做。用戶體驗好,即時反饋,不用等網絡往返。
但前端校驗能不能當安全防線?不能。因為前端校驗可以被繞過——隨便開個Postman直接打接口,你的前端校驗跟沒有一樣。
所以正確的做法是:前端做L1到L4,體驗層面攔一道。后端L1到L5全部重跑一遍,這是安全兜底。 L5本來就要查庫,只能在后端做。
別覺得后端重復跑一遍是浪費。安全領域有個原則叫”縱深防御”——不是一道墻夠高就行,是多道墻疊在一起,每道都有可能攔住一類攻擊。校驗也是一樣。
有些團隊為了省事,前端做了校驗后端就不做了。我只能說,等哪天被人用腳本往你接口里灌臟數據的時候,你就知道這個偷懶有多貴了。
說到底,校驗設計這件事沒有什么花活。就一條原則:
先做便宜的判斷,再做貴的判斷。先用本地信息,再用外部信息。先查格式,再查語義。
把這條刻進DNA里,不管是寫表單、設計API、搭數據管道還是做消息消費,你的校驗邏輯都不會太離譜。
至于那個把查庫放在第一步的同事——他后來重構了。現在那段代碼的注釋寫著:
“L1→L5,別改順序。改了請客。”
本文來自公眾號:尤里卡高 作者:尤里卡高
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.