記錄--如何優雅地校驗後端介面數據

来源:https://www.cnblogs.com/smileZAZ/archive/2023/02/28/17165435.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 背景 最近新接手了一批項目,還沒來得及接新需求,一大堆bug就接踵而至,仔細一看,應該返回數組的欄位返回了 null,或者沒有返回,甚至返回了字元串 "null"??? 這我能忍?我立刻截圖發到群里,用紅框加大加粗重點標出。後端同學也積極 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

背景

最近新接手了一批項目,還沒來得及接新需求,一大堆bug就接踵而至,仔細一看,應該返回數組的欄位返回了 null,或者沒有返回,甚至返回了字元串 "null"???

這我能忍?我立刻截圖發到群里,用紅框加大加粗重點標出。後端同學也積極響應,答應改正。

image.png

第二天,同樣的事情又在其他的項目上演,我只是一個小前端,為什麼什麼錯都找我啊!!

日子不能再這樣下去,於是我決定寫一個工具來解決遇到 bug 永遠在找前端的困境。

TypeScript 運行時校驗

如何對介面數據進行校驗呢,因為我們的項目是 React+TypeScript 寫的,所以第一時間就想到了使用 TypeScript 進行數據校驗。但是眾所周知,TypeScript 用於編譯時校驗,有沒有辦法作用到運行時呢?

我還真找到了一些運行時類型校驗的庫:typescript-needs-types,大部分需要使用指定格式編寫代碼,相當於對項目進行重構,拿其中 star 最多的 zod 舉例,代碼如下。

import { z } from "zod";

const User = z.object({
  username: z.string(),
});

User.parse({ username: "Ludwig" });

// extract the inferred type
type User = z.infer<typeof User>;
// { username: string }

我寧可查 bug 也不可能重構手裡一大堆項目啊。此種方案 ❎。

此時看到了 typescript-json-schema 可以把 TypeScript 定義轉為 JSON Schema ,然後再使用 JSON Schema 對數據進行校驗就可以啦。這種方案比較靈活,且對代碼入侵性較小。

搭建一個項目測試一下!

使用 npx create-react-app my-app --template typescript 快速創建一個 React+TS 項目。

首先安裝依賴 npm install typescript-json-schema

創建類型文件 src/types/user.ts

export interface IUserInfo {
  staffId: number
  name: string
  email: string
}

然後創建 src/types/index.ts 文件並引入剛纔的類型。

import { IUserInfo } from './user';

interface ILabel {
  id: number;
  name: string;
  color: string;
  remark?: string;
}

type ILabelArray = ILabel[];

type IUserInfoAlias = IUserInfo;

接下來在 package.json 添加腳本

"scripts": {
    // ...
    "json": "typescript-json-schema src/types/index.ts '*' -o src/types/index.json --id=api --required --strictNullChecks"
}
然後運行 npm run json 可以看到新建了一個 src/types/index.json 文件(此步在已有項目中可能會報錯報錯,可以嘗試在 json 命令中添加 --ignoreErrors 參數),打開文件可以看到已經成功轉成了 JSON Schema 格式。
{
    "$id": "api",
    "$schema": "http://json-schema.org/draft-07/schema#",
    "definitions": {
        "ILabel": {
            "properties": {
                "color": {
                    "type": "string"
                },
                "id": {
                    "type": "number"
                },
                "name": {
                    "type": "string"
                },
                "remark": {
                    "type": "string"
                }
            },
            "required": [
                "color",
                "id",
                "name"
            ],
            "type": "object"
        },
        "ILabelArray": {
            "items": {
                "$ref": "api#/definitions/ILabel"
            },
            "type": "array"
        },
        "IUserInfoAlias": {
            "properties": {
                "email": {
                    "type": "string"
                },
                "name": {
                    "type": "string"
                },
                "staffId": {
                    "type": "number"
                }
            },
            "required": [
                "email",
                "name",
                "staffId"
            ],
            "type": "object"
        }
    }
}

使用 JSON Schema 校驗數據

至於如何使用JSON Schema 校驗數據,我找到了現成的庫 ajv,至於為什麼選擇 ajv,主要是因為它說它很快,詳見:github.com/ebdrup/json…

接下來嘗試一下。我找到了中文版文檔,有興趣的可以去看下 www.febeacon.com/ajv-docs-zh…

先安裝依賴 npm install ajv,然後創建文件 src/validate.ts

import Ajv from 'ajv';
import schema from './types/index.json';

const ajv = new Ajv({ schemas: [schema] });

export function validateDataByType(type: string, data: unknown) {
  console.log(`開始校驗,類型:${type}, 數據:`, data);

  var validate = ajv.getSchema(`api#/definitions/${type}`);
  if (validate) {
    const valid = validate(data);
    if (!valid) {
      console.log('校驗失敗', validate.errors);
    }
    else {
      console.log('校驗成功');
    }
  }
}

接下來在 src/index.tsx 添加下麵代碼來測試一下。

validateDataByType('IUserInfoAlias', {
  email: '[email protected]',
  name: 'idonteatcookie',
  staffId: 12306
})

validateDataByType('IUserInfoAlias', {
  email: '[email protected]',
  staffId: 12306
})

validateDataByType('IUserInfoAlias', {
  email: '[email protected]',
  name: 'idonteatcookie',
  staffId: '12306'
})

可以在控制台看到成功列印如下信息:

攔截請求

因為項目中發送請求都是調用統一封裝的函數,所以我首先想到的是在函數中增加一層校驗邏輯。但是這樣的話就與項目代碼耦合嚴重,換一個項目又要再寫一份。我真的有好多項目QAQ。

那乾脆攔截所有請求統一處理好了。

很容易的找到了攔截所有 XMLHttpRequest 請求的庫 ajax-hook,可以非常簡單地對請求做處理。

首先安裝依賴 npm install ajax-hook,然後創建 src/interceptTool.ts

import { proxy } from 'ajax-hook';
export function intercept() {
  // 獲取 XMLHttpRequest 發送的請求
  proxy({
    onResponse: (response: any, handler: any) => {
      console.log('xhr', response.response)
      handler.next(response);
    },
  });
}

這樣就攔截了所有的 XMLHttpRequest 發送的請求,但是我突然想到我們的項目,好像使用 fetch 發送的請求來著???

好叭,那就再攔截一遍 fetch 發送的請求。

export function intercept() {
  // ...
  const { fetch: originalFetch } = window;
  // 獲取 fetch 發送的請求
  window.fetch = async (...args) => {
    const response = await originalFetch(...args);
    response.clone().json().then((data: { result: any }) => {
      console.log('window.fetch', args, data);
      return data;
    });
    return response;
  };
}

為了證明攔截成功,使用 json-server 搭建一個本地 mock 伺服器。首先安裝 npm install json-server,然後在根目錄創建文件 db.json

{
  "user": { "staffId": 1, "name": "cookie1", "email": "[email protected]" },
  "labels": [
    {
      "id": 1,
      "name": "ck",
      "color": "red",
      "remark": "blabla"
    },
    {
      "id": 2,
      "color": "green"
    }
  ]
}

再在 package.json 添加腳本

"scripts": {
  "serve": "json-server --watch db.json -p 8000"
},

現在執行 npm run serve 就可以啟動伺服器了。在 src/index.tsx 增加調用介面的代碼,並引入 src/interceptTool.ts

import { intercept } from './interceptTool';
// ... other code
intercept();

fetch('http://localhost:8000/user');

const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:8000/labels');
xhr.send();

可以看到兩種請求都攔截成功了。

校驗介面返回數據

勝利在望,只差最後一步,校驗返回數據。我們校驗數據需要提供兩個關鍵信息,數據本身和對應的類型名,為了將兩者對應起來,需要再創建一個映射文件,把 url 和類型名對應起來。

創建文件 src/urlMapType.ts 然後添加內容

export const urlMapType = {
  'http://localhost:8000/user': 'IUserInfoAlias',
  'http://localhost:8000/labels': 'ILabelArray',
}

我們在 src/validate.ts 新增函數 validateDataByUrl

import { urlMapType } from './urlMapType';
// ...
export function validateDataByUrl(url: string, data: unknown) {
  const type = urlMapType[url as keyof typeof urlMapType];
  if (!type) {
    // 沒有定義對應格式不進行校驗
    return;
  }
  console.log(`==== 開始校驗 === url ${url}`);
  validateDataByType(type, data);
}

然後在 src/interceptTool.ts 文件中引用

import { proxy } from 'ajax-hook';
import { validateDataByUrl } from './validate';

export function intercept() {
  // 獲取 XMLHttpRequest 發送的請求
  proxy({
    onResponse: (response, handler: any) => {
      validateDataByUrl(response.config.url, JSON.parse(response.response));
      handler.next(response);
    },
  });

  const { fetch: originalFetch } = window;
  // 獲取 fetch 發送的請求
  window.fetch = async (...args) => {
    const response = await originalFetch(...args);
    response.json().then((data: any) => {
      validateDataByUrl(args[0] as string, data);
      return data;
    });
    return response;
  };
}

現在可以在控制台看到介面數據校驗的介面辣~ ✿✿ヽ(°▽°)ノ✿

 

 總結下流程圖

後續規劃

目前所做的事情,準確的說不是攔截,只是獲取返回數據,然後對比列印校驗結果,因為初步目標不涉及數據的處理。

後續會考慮對不合法的數據進行處理,比如應該返回數組但是返回了 null 的情況,如果能自動賦值 [],就可以防止前端頁面崩潰的情況了。

本文轉載於:

https://juejin.cn/post/7166061734803963917

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • sqlite3資料庫是一個小型的資料庫,當數據量不大,要求不是特別高的時候,是個不錯的選擇。 在Linux上移植和使用也非常的方便。 本示例是在硬體全志r528 .linux5.4 上驗證的。 移植操作: 1、源碼下載 去官網進行下載源碼SQLite Download Page,根據自己的選取選擇不 ...
  • 摘要:GaussDB目前採用的FIFO調度機制,該調度機制無法滿足用戶的網路隔離需求和QoS需求,同時FIFO調度可能帶來比較嚴重的抖動。 本文分享自華為雲社區《【玩轉PB級數倉GaussDB(DWS)】GaussDB(DWS)網路調度與隔離管控能力》,作者:門前一棵葡萄樹 。 一、常見的調度演算法 ...
  • 最近工作中需要部署一套資料庫服務到內網伺服器上,藉此機會,我重新整理了postgresql資料庫的搭建及入門使用方法 1、安裝方式(兩種選一種) a、第一種方式 sudo yum install -y https://download.postgresql.org/pub/repos/yum/rep ...
  • 經濟全球化的今天,人們在工作和生活中經常會與外語打交道。相較傳播性較廣的英語而言,其他語種的識別和閱讀對大多數人來說是一件難事,此時就需要藉助語言翻譯軟體來幫助理解。 華為 HMS Core 機器學習服務(ML Kit)翻譯功能提供了多種翻譯模式,不僅可以滿足應用出行購物、網路社交等日常場景,還提供 ...
  • 原文:Jetpack Compose學習(11)——Navigation頁面導航的使用 - Stars-One的雜貨小窩 在Android原生的View開發中的,也是有Navigation,原生我之後可能再出篇教程,今天講解的則是compose版本的Navigation組件的使用 本系列以往文章請查 ...
  • HTML元素 空元素 不是所有元素都擁有開始標簽、內容和結束標簽。一些元素只有一個標簽,通常用來在此元素所在位置插入/嵌入一些東西。這些元素被稱為空元素例如:元素 `` 是用來在頁面插入一張指定的圖片。 布爾屬性 有時會看到沒有值的屬性,這也是完全可以接受的。這些屬性被稱為布爾屬性。布爾屬性只能有一 ...
  • 如果希望自己的代碼更優雅、可維護性更高以及更簡潔,往往離不開設計模式這一解決方案。 在JS設計模式中,最核心的思想:封裝變化(將變與不變分離,確保變化的部分靈活,不變的部分穩定)。 單例模式 那麼來說說第一個常見的設計模式:單例模式。 單例模式保證一個類僅有一個實例,並提供一個訪問它的全局訪問方式, ...
  • 隨著項目的不斷維護,代碼越來越多,項目越來越大,決定將老項目遷移至vite。本文介紹了Vue老項目像Vite遷移的過程、遇到的問題以及經驗總結。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...