精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Go 優(yōu)秀實(shí)踐:請(qǐng)求參數(shù)校驗(yàn)方法設(shè)計(jì)和實(shí)現(xiàn)

開(kāi)發(fā) 后端
本文詳細(xì)介紹了在 Go 項(xiàng)目開(kāi)發(fā)中如何實(shí)現(xiàn) API 接口請(qǐng)求參數(shù)的校驗(yàn)邏輯,并以 miniblog 項(xiàng)目為例進(jìn)行了實(shí)踐。

本節(jié)課會(huì)詳細(xì)介紹下 gRPC 請(qǐng)求的請(qǐng)求參數(shù)校驗(yàn)邏輯實(shí)現(xiàn)。

一、為什么要 API 接口請(qǐng)求參數(shù)

對(duì) API 請(qǐng)求參數(shù)進(jìn)行校驗(yàn)是 Web 開(kāi)發(fā)中需要實(shí)現(xiàn)的一個(gè)核心功能之一,它不僅能夠提升系統(tǒng)的可靠性,還可以提高用戶體驗(yàn)、數(shù)據(jù)安全性以及代碼的可維護(hù)性。以下是具體原因的介紹:

(1) 保證系統(tǒng)的穩(wěn)定性:API 接收到的請(qǐng)求參數(shù)可能從客戶端或第三方應(yīng)用發(fā)起,這些參數(shù)可能由于客戶端開(kāi)發(fā)錯(cuò)誤、意外修改或惡意構(gòu)造而不符合預(yù)期。如果未對(duì)請(qǐng)求參數(shù)進(jìn)行校驗(yàn),可能導(dǎo)致系統(tǒng)邏輯錯(cuò)誤,甚至出現(xiàn)程序崩潰,從而影響服務(wù)的可用性。例如:未校驗(yàn)分頁(yè)參數(shù),可能引發(fā)數(shù)據(jù)庫(kù)查詢性能急劇下滑,如負(fù)數(shù)頁(yè)碼或極大的 limit;

(2) 確保數(shù)據(jù)的合法性和完整性:無(wú)論是前端用戶輸入還是對(duì)接方系統(tǒng)調(diào)用,都有可能提交不符合業(yè)務(wù)要求的數(shù)據(jù),如必填字段缺失、字符串格式不正確、超出預(yù)期范圍等。如果直接寫(xiě)入數(shù)據(jù)庫(kù)或業(yè)務(wù)邏輯處理,可能會(huì)產(chǎn)生錯(cuò)誤數(shù)據(jù),導(dǎo)致后續(xù)問(wèn)題難以排查;

(3) 增強(qiáng)用戶體驗(yàn):不進(jìn)行參數(shù)校驗(yàn),錯(cuò)誤通常會(huì)發(fā)生在邏輯處理階段(如存儲(chǔ)數(shù)據(jù)庫(kù)層或業(yè)務(wù)邏輯層),錯(cuò)誤提示可能與用戶的實(shí)際問(wèn)題無(wú)關(guān),而是以難以理解的系統(tǒng)錯(cuò)誤呈現(xiàn)。這不僅難以定位問(wèn)題,還會(huì)讓用戶感到困惑。通過(guò)校驗(yàn)參數(shù),可以在第一時(shí)間返回清晰的錯(cuò)誤信息,告訴用戶問(wèn)題所在,改善用戶體驗(yàn)。例如"username"字段為空時(shí)提示:"用戶名不能為空";

(4) 維護(hù)代碼的清晰性和可維護(hù)性:沒(méi)有參數(shù)校驗(yàn)的代碼通常需要開(kāi)發(fā)者在業(yè)務(wù)邏輯部分反復(fù)進(jìn)行參數(shù)檢查,例如空值判斷、格式驗(yàn)證、一層層的數(shù)據(jù)過(guò)濾,這會(huì)導(dǎo)致業(yè)務(wù)邏輯代碼雜亂且難以維護(hù)。通過(guò)集中化參數(shù)校驗(yàn):

  • 將參數(shù)校驗(yàn)邏輯從業(yè)務(wù)邏輯中剝離,保持代碼簡(jiǎn)潔;
  • 參數(shù)檢查可以在控制器層完成,使核心業(yè)務(wù)處理代碼得到解耦。

(5) 服務(wù)端可信性原則:在開(kāi)發(fā)中,應(yīng)遵循“永遠(yuǎn)不要完全信任客戶端”的原則。即使在客戶端已做校驗(yàn)(如前端的表單必填檢查),也必須在后端進(jìn)行校驗(yàn)。

對(duì) API 請(qǐng)求參數(shù)進(jìn)行校驗(yàn),最核心的目的是提升系統(tǒng)的健壯性和安全性,提供良好的用戶體驗(yàn)并減少錯(cuò)誤傳播。在 Go 項(xiàng)目開(kāi)發(fā)中,服務(wù)端必須對(duì)所有來(lái)自客戶端的數(shù)據(jù)進(jìn)行嚴(yán)格校驗(yàn),確保系統(tǒng)處于受控狀態(tài)。

二、API 接口請(qǐng)求參數(shù)校驗(yàn)方法

API 接口請(qǐng)求參數(shù)校驗(yàn)方法有多種。本節(jié)會(huì)介紹這些校驗(yàn)方法,并結(jié)合真實(shí)場(chǎng)景下的請(qǐng)求參數(shù)校驗(yàn)需求,實(shí)現(xiàn) miniblog 的請(qǐng)求參數(shù)校驗(yàn)方法。具體來(lái)說(shuō),有以下幾種請(qǐng)求參數(shù)校驗(yàn)方法:

  • 手動(dòng)校驗(yàn);
  • 第三方校驗(yàn)庫(kù);
  • 使用 Web 框架內(nèi)置校驗(yàn)功能;
  • 基于工具生成校驗(yàn)代碼;
  • 中間件校驗(yàn)。

在實(shí)際開(kāi)發(fā)中,不少開(kāi)發(fā)者會(huì)同時(shí)使用上述校驗(yàn)方法中的兩種或更多種,導(dǎo)致項(xiàng)目的校驗(yàn)方式不夠規(guī)范和統(tǒng)一,從而增加了代碼閱讀的難度,降低了開(kāi)發(fā)效率,并提高了維護(hù)成本。導(dǎo)致同時(shí)使用多種校驗(yàn)方式的原因有多方面,例如項(xiàng)目缺乏統(tǒng)一的校驗(yàn)規(guī)范,開(kāi)發(fā)者隨意選擇自己偏好的校驗(yàn)方法,或者現(xiàn)有的校驗(yàn)方式在形式和功能上無(wú)法完全滿足項(xiàng)目的實(shí)際需求。

所以,miniblog 項(xiàng)目結(jié)合實(shí)際 Go 項(xiàng)目開(kāi)發(fā)中的業(yè)務(wù)校驗(yàn)場(chǎng)景,設(shè)計(jì)一種更加通用和標(biāo)準(zhǔn)化的 API 接口請(qǐng)求參數(shù)校驗(yàn)方法。

1. 手動(dòng)校驗(yàn)

手動(dòng)校驗(yàn)指的是直接在代碼中判斷參數(shù)是否合法。這種方法適用于簡(jiǎn)單的項(xiàng)目,不需要引入額外工具或包,但維護(hù)成本較高,不適合復(fù)雜的項(xiàng)目。

代碼清單 10-1 展示了一個(gè)手動(dòng)校驗(yàn)的代碼示例。

代碼清單 10-1 手動(dòng)校驗(yàn):

package main  

import (  
    "errors"  
    "fmt"  
)  

type LoginRequest struct {  
    Username string  
    Password string  
}  

func validate(req LoginRequest) error {  
    if req.Username == "" {  
        return errors.New("username is required")  
    }  
    if len(req.Password) < 6 {  
        return errors.New("password must be at least 6 characters long")  
    }  
    return nil  
}  

func main() {  
    req := LoginRequest{Username: "user", Password: "12345"}  
    if err := validate(req); err != nil {  
        fmt.Println("Validation failed:", err)  
        return  
    }  
    fmt.Println("Validation passed")  
}

2. 第三方校驗(yàn)庫(kù)

Go 項(xiàng)目有許多成熟且功能強(qiáng)大的第三方參數(shù)校驗(yàn)庫(kù),這些校驗(yàn)庫(kù)根據(jù)結(jié)構(gòu)體標(biāo)簽來(lái)進(jìn)行字段校驗(yàn)。例如常用的校驗(yàn)庫(kù)包括:go-playground/validator(常用)、asaskevich/govalidator、ozzo-validation 等。

這些庫(kù)提供了豐富的校驗(yàn)規(guī)則(如必填字段、正則表達(dá)式、數(shù)值范圍等),還支持自定義規(guī)則并可自動(dòng)處理嵌套結(jié)構(gòu)體。

代碼清單 10-2 展示了使用使用 go-playground/validator 進(jìn)行請(qǐng)求參數(shù)校驗(yàn)的代碼示例。

代碼清單 10-2 第三方校驗(yàn)庫(kù):

package main  

import (  
    "fmt"  
    "github.com/go-playground/validator/v10"  
)  

type LoginRequest struct {  
    Username string `validate:"required"`          // 必填字段  
    Password string `validate:"required,min=6"`    // 最小長(zhǎng)度為6  
    Email    string `validate:"required,email"`    // 必填且必須是郵箱格式  
}  

func main() {  
    validate := validator.New() // 創(chuàng)建驗(yàn)證器  

    req := LoginRequest{  
        Username: "user",  
        Password: "12345",  
        Email:    "invalid-email",  
    }  

    // 校驗(yàn)結(jié)構(gòu)體  
    err := validate.Struct(req)  
    if err != nil {  
        // 獲取校驗(yàn)錯(cuò)誤并打印  
        for _, err := range err.(validator.ValidationErrors) {  
            fmt.Printf("Field '%s' failed validation, rule '%s'\n", err.Field(), err.Tag())  
        }  
    } else {  
        fmt.Println("Validation passed!")  
    }  
}

使用第三方驗(yàn)證庫(kù)校驗(yàn),優(yōu)點(diǎn)是可以直接復(fù)用現(xiàn)成的校驗(yàn)邏輯,并且直接基于結(jié)構(gòu)體標(biāo)簽來(lái)進(jìn)行驗(yàn)證,更加高效,代碼更加簡(jiǎn)潔。但缺點(diǎn)是缺乏靈活性,難以滿足復(fù)雜的校驗(yàn)場(chǎng)景。

3. 使用 Web 框架內(nèi)置校驗(yàn)功能

Gin 框架支持結(jié)合 go-playground/validator 的校驗(yàn),在處理請(qǐng)求數(shù)據(jù)時(shí),利用 binding 標(biāo)簽可以直接解析和校驗(yàn)。示例代碼如代碼清單 10-3 所示。

代碼清單 10-3 使用 Web 框架內(nèi)置功能:

package main  

import (  
    "net/http"  

    "github.com/gin-gonic/gin"  
    "github.com/go-playground/validator/v10"  
)  

type LoginRequest struct {  
    Username string `json:"username" binding:"required"`  
    Password string `json:"password" binding:"required,min=6"`  
}  

func main() {  
    r := gin.Default()  

    r.POST("/login", func(c *gin.Context) {  
        var req LoginRequest  
        if err := c.ShouldBindJSON(&req); err != nil {  
            // 返回校驗(yàn)錯(cuò)誤  
            errs := err.(validator.ValidationErrors)  
            c.JSON(http.StatusBadRequest, gin.H{"error": errs.Error()})  
            return  
        }  

        // 校驗(yàn)通過(guò)  
        c.JSON(http.StatusOK, gin.H{"message": "Login successful"})  
    })  

    r.Run()  
}

使用 Web 框架自帶的校驗(yàn)功能,簡(jiǎn)單便捷,但無(wú)法滿足復(fù)雜的校驗(yàn)場(chǎng)景。

4. 基于工具生成校驗(yàn)代碼

在一些大型項(xiàng)目中,可以使用工具自動(dòng)生成校驗(yàn)規(guī)則(例如基于 OpenAPI/Swagger 的定義),通過(guò)自動(dòng)化的方式生成校驗(yàn)邏輯,減少手動(dòng)編寫(xiě)的工作量,提高開(kāi)發(fā)效率和代碼一致性。常用的工具包括:

  • OpenAPI Generator:支持根據(jù) OpenAPI 描述生成 Go 代碼,包括參數(shù)校驗(yàn)邏輯;
  • gqlgen(GraphQL 工具):自動(dòng)生成 API 代碼,其中包含參數(shù)校驗(yàn)等功能。

使用工具生成校驗(yàn)代碼優(yōu)點(diǎn)是簡(jiǎn)單便捷,開(kāi)發(fā)工作量小。但缺點(diǎn)也是無(wú)法滿足真實(shí)企業(yè)應(yīng)用開(kāi)發(fā)中,遇到的復(fù)雜校驗(yàn)場(chǎng)景。

5. 中間件校驗(yàn)

在某些情況下,可以將校驗(yàn)邏輯抽象為中間件處理,比如 Token 校驗(yàn)、權(quán)限校驗(yàn)、固定格式的參數(shù)校驗(yàn)等,例如:

func ValidationMiddleware(next http.Handler) http.Handler {  
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  
        if r.Header.Get("X-API-Key") == "" {  
            http.Error(w, "Missing API Key", http.StatusUnauthorized)  
            return  
        }  
        next.ServeHTTP(w, r)  
    })  
}

三、miniblog 請(qǐng)求參數(shù)校驗(yàn)設(shè)計(jì)

在實(shí)際的 Go 項(xiàng)目開(kāi)發(fā)中,對(duì)于接口請(qǐng)求參數(shù)校驗(yàn)方法的的一般訴求如下:

  • 支持自定義復(fù)雜校驗(yàn)邏輯:能夠根據(jù)具體需求定義復(fù)雜的參數(shù)校驗(yàn)規(guī)則。這些規(guī)則可能超出簡(jiǎn)單的數(shù)據(jù)長(zhǎng)度或大小校驗(yàn)的范疇,例如需要通過(guò)查詢數(shù)據(jù)庫(kù)驗(yàn)證記錄是否存在,或依賴與第三方微服務(wù)的交互來(lái)完成復(fù)雜的校驗(yàn)邏輯;
  • 復(fù)用已有的參數(shù)校驗(yàn)邏輯:支持將某個(gè)參數(shù)的校驗(yàn)邏輯封裝并復(fù)用。例如,用戶密碼的校驗(yàn)邏輯在創(chuàng)建用戶時(shí)需要用到,修改用戶密碼時(shí)同樣適用。這種情況下,校驗(yàn)規(guī)則應(yīng)在不同接口間保持一致性,避免重復(fù)實(shí)現(xiàn);
  • 靈活通用的校驗(yàn)方式:允許根據(jù)不同場(chǎng)景靈活調(diào)整校驗(yàn)邏輯,使請(qǐng)求參數(shù)校驗(yàn)更具通用性,適應(yīng)多樣化的需求場(chǎng)景,提升開(kāi)發(fā)效率與代碼維護(hù)性。
  • 校驗(yàn)方式簡(jiǎn)單易維護(hù):校驗(yàn)方式需要簡(jiǎn)單,并且容易維護(hù)。

基于上述需求,miniblog 項(xiàng)目設(shè)計(jì)了以下請(qǐng)求參數(shù)校驗(yàn)方案:

  • 校驗(yàn)方式易維護(hù):項(xiàng)目中所有 API 請(qǐng)求參數(shù)校驗(yàn)邏輯集中保存在 internal/apiserver/pkg/validation 目錄下。不同資源的校驗(yàn)邏輯保存在不同的源碼文件中,便于查閱和維護(hù)各資源的校驗(yàn)邏輯。
  • 校驗(yàn)方式標(biāo)準(zhǔn)化:所有請(qǐng)求接口的校驗(yàn)函數(shù)聲明為統(tǒng)一的規(guī)范格式,例如:Validate<請(qǐng)求參數(shù)結(jié)構(gòu)體名>(ctx context.Context, rq *apiv1.<請(qǐng)求參數(shù)結(jié)構(gòu)體名>) error;
  • 支持自定義校驗(yàn)邏輯:通過(guò)創(chuàng)建專(zhuān)門(mén)的校驗(yàn)類(lèi)型,將數(shù)據(jù)庫(kù)連接、第三方微服務(wù)客戶端、緩存客戶端等依賴實(shí)例注入到校驗(yàn)類(lèi)型的實(shí)例中。在自定義校驗(yàn)邏輯中,使用這些依賴實(shí)例,進(jìn)行復(fù)雜的邏輯校驗(yàn);
  • 支持靈活的校驗(yàn)方法:既支持復(fù)雜的自定義校驗(yàn)邏輯,又支持復(fù)用某個(gè)請(qǐng)求參數(shù)的校驗(yàn)邏輯。

因?yàn)檎?qǐng)求參數(shù)校驗(yàn),幾乎是每個(gè)接口都需要的功能,所以最理想的情況是通過(guò) Web 中間件來(lái)校驗(yàn)請(qǐng)求參數(shù)。基于此設(shè)計(jì)思路,設(shè)計(jì)了 miniblog 的請(qǐng)求參數(shù)校驗(yàn)方案,如圖 10-2 所示。

圖 10-2 請(qǐng)求參數(shù)校驗(yàn)設(shè)計(jì):

圖 10-2 中,定義一個(gè) Validator 結(jié)構(gòu)體類(lèi)型,結(jié)構(gòu)體類(lèi)型中包含了自定義請(qǐng)求參數(shù)校驗(yàn)需要的各類(lèi)依賴項(xiàng)。Validator 結(jié)構(gòu)體類(lèi)型包含了格式如 ValidateXXX(ctx context.Context, rq *apiv1.XXX) error 的請(qǐng)求參數(shù)校驗(yàn)方法,用來(lái)對(duì)名為 XXX 的請(qǐng)求參數(shù)結(jié)構(gòu)體類(lèi)型進(jìn)行校驗(yàn)。為了提高項(xiàng)目的可維護(hù)性,建議 XXX 的命名格式為 <接口名>Request,例如 Login 接口的參數(shù)校驗(yàn)方法為:ValidateLoginRequest(ctx context.Context, rq *apiv1.LoginRequest) error。

圖 10-2 中,封裝了一個(gè)通用校驗(yàn)層,通用校驗(yàn)層會(huì)解析 Validator 類(lèi)型的實(shí)例,遍歷該實(shí)例中的所有方法,并提取出格式為 ValidateXXX(ctx context.Context, rq *apiv1.XXX) error 的方法,將這些方法保存在一個(gè) map 類(lèi)型的變量中,鍵為請(qǐng)求參數(shù)結(jié)構(gòu)體名,值為校驗(yàn)方法本身。

Web 中間件層,通過(guò)通用校驗(yàn)層來(lái)對(duì)接口進(jìn)行驗(yàn)證。在校驗(yàn)請(qǐng)求參數(shù)時(shí),根據(jù)請(qǐng)求參數(shù)類(lèi)型名,從通用校驗(yàn)層中查找鍵為類(lèi)型名的鍵值對(duì),并調(diào)用值(校驗(yàn)方法)進(jìn)行參數(shù)校驗(yàn)。

通用校驗(yàn)層提供了 ValidateAllFields(obj any, rules Rules) error 函數(shù),該函數(shù)支持復(fù)用某個(gè)請(qǐng)求參數(shù)的校驗(yàn)邏輯,下文會(huì)詳細(xì)介紹。

四、miniblog 請(qǐng)求參數(shù)校驗(yàn)實(shí)現(xiàn)

上一節(jié)介紹了 miniblog 項(xiàng)目的請(qǐng)求參數(shù)校驗(yàn)設(shè)計(jì)方案。本節(jié)將詳細(xì)說(shuō)明 miniblog 是如何實(shí)現(xiàn)這些校驗(yàn)方案的。

miniblog 項(xiàng)目同時(shí)支持基于 Gin 框架的 HTTP 服務(wù)器和基于 gRPC 框架的 RPC 服務(wù)器。由于兩種服務(wù)器類(lèi)型在請(qǐng)求處理中間件層能獲取到的請(qǐng)求信息不同,因此在實(shí)現(xiàn)請(qǐng)求參數(shù)校驗(yàn)邏輯時(shí)也有所區(qū)別。

1. 實(shí)現(xiàn)請(qǐng)求參數(shù)校驗(yàn)方法

在 internal/apiserver/pkg/validation/validation.go 文件中,定義了 Validator 結(jié)構(gòu)體類(lèi)型,該類(lèi)型包含了自定義校驗(yàn)邏輯中需要的各類(lèi)依賴項(xiàng),以及用來(lái)校驗(yàn)請(qǐng)求參數(shù)的各類(lèi)校驗(yàn)方法。Validator 結(jié)構(gòu)體類(lèi)型定義如下:

// Validator 是驗(yàn)證邏輯的實(shí)現(xiàn)結(jié)構(gòu)體.
type Validator struct {
    // 有些復(fù)雜的驗(yàn)證邏輯,可能需要直接查詢數(shù)據(jù)庫(kù)
    // 這里只是一個(gè)舉例,如果驗(yàn)證時(shí),有其他依賴的客戶端/服務(wù)/資源等,
    // 都可以一并注入進(jìn)來(lái)
    store store.IStore
}

Post 資源相關(guān)接口的請(qǐng)求參數(shù)校驗(yàn)方法實(shí)現(xiàn)位于 internal/apiserver/pkg/validation/post.go 文件中,校驗(yàn)方法實(shí)現(xiàn)如代碼清單 10-4 所示。

代碼清單 10-4 Post 資源請(qǐng)求參數(shù)校驗(yàn)方法實(shí)現(xiàn):

// ValidateCreatePostRequest 校驗(yàn) CreatePostRequest 結(jié)構(gòu)體的有效性.
func (v *Validator) ValidateCreatePostRequest(ctx context.Context, rq *apiv1.CreatePostRequest) error {
    return genericvalidation.ValidateAllFields(rq, v.ValidatePostRules())
}

// ValidateUpdatePostRequest 校驗(yàn)更新用戶請(qǐng)求.
func (v *Validator) ValidateUpdatePostRequest(ctx context.Context, rq *apiv1.UpdatePostRequest) error {
    return genericvalidation.ValidateAllFields(rq, v.ValidatePostRules())
}

// ValidateDeletePostRequest 校驗(yàn) DeletePostRequest 結(jié)構(gòu)體的有效性.
func (v *Validator) ValidateDeletePostRequest(ctx context.Context, rq *apiv1.DeletePostRequest) error {
    return genericvalidation.ValidateAllFields(rq, v.ValidatePostRules())
}

// ValidateGetPostRequest 校驗(yàn) GetPostRequest 結(jié)構(gòu)體的有效性.
func (v *Validator) ValidateGetPostRequest(ctx context.Context, rq *apiv1.GetPostRequest) error {
    return genericvalidation.ValidateAllFields(rq, v.ValidatePostRules())
}

// ValidateListPostRequest 校驗(yàn) ListPostRequest 結(jié)構(gòu)體的有效性.
func (v *Validator) ValidateListPostRequest(ctx context.Context, rq *apiv1.ListPostRequest) error {
    if rq.Title != nil && len(rq.Title) > 200 {
        return errno.ErrInvalidArgument.WithMessage("title cannot be longer than 200 characters")
    }
    return genericvalidation.ValidateSelectedFields(rq, v.ValidatePostRules(), "Offset", "Limit")
}

代碼清單 10-4 實(shí)現(xiàn)了 Post 資源 CreatePost、UpdatePost、GetPost、ListPost 接口的請(qǐng)求參數(shù)校驗(yàn)邏輯。

ValidateAllFields 函數(shù)用來(lái)對(duì)請(qǐng)求參數(shù)中的所有字段進(jìn)行校驗(yàn),其中每個(gè)字段的校驗(yàn)規(guī)則在 ValidatePostRules 方法中設(shè)置。ValidatePostRules 方法實(shí)現(xiàn)如下:

// Validate 校驗(yàn)字段的有效性.
func (v *Validator) ValidatePostRules() genericvalidation.Rules {
    // 定義各字段的校驗(yàn)邏輯,通過(guò)一個(gè) map 實(shí)現(xiàn)模塊化和簡(jiǎn)化
    return genericvalidation.Rules{
        "PostID": func(value any) error {
            if value.(string) == "" {
                return errno.ErrInvalidArgument.WithMessage("postID cannot be empty")
            }
            return nil
        },
        "Title": func(value any) error {
            if value.(string) == "" {
                return errno.ErrInvalidArgument.WithMessage("title cannot be empty")
            }
            return nil
        },
        "Content": func(value any) error {
            if value.(string) == "" {
                return errno.ErrInvalidArgument.WithMessage("content cannot be empty")
            }
            return nil
        },
    }
}

代碼清單 10-4 的 ValidateListPostRequest 方法調(diào)用了 ValidateSelectedFields 函數(shù),該函數(shù)只會(huì)校驗(yàn)傳入的字段 Offset、Limit。apiv1.ListPostRequest 結(jié)構(gòu)體中其他字段,例如 Title 字段的校驗(yàn),可以自行實(shí)現(xiàn)校驗(yàn)邏輯,通過(guò)這種方式,允許開(kāi)發(fā)者根據(jù)需要選擇,哪些字段使用通用的字段校驗(yàn)規(guī)則校驗(yàn),哪些字段自行實(shí)現(xiàn)校驗(yàn)邏輯,以此滿足復(fù)雜的字段校驗(yàn)邏輯。

這里要注意,如果指定了校驗(yàn) NonExist 字段,但 NonExist 字段沒(méi)有在 apiv1.ListPostRequest 結(jié)構(gòu)體存在,則 ValidateSelectedFields 函數(shù)會(huì)跳過(guò) NonExist 字段的校驗(yàn)。

另外,ValidateAllFields、ValidateSelectedFields 函數(shù)在校驗(yàn)時(shí),如果結(jié)構(gòu)體中的某個(gè)字段不存在對(duì)應(yīng)的校驗(yàn) Rule,則函數(shù)會(huì)跳過(guò)該字段的校驗(yàn)。通過(guò)給字段(例如 PostID、Title、Content)指定相同的校驗(yàn)規(guī)則,來(lái)保證不同 API 接口相同字段的校驗(yàn)邏輯一致性。

2. HTTP 請(qǐng)求參數(shù)校驗(yàn)

在 Gin 中間件中,無(wú)法提前獲知 API 的請(qǐng)求參數(shù)類(lèi)型,所以無(wú)法實(shí)現(xiàn)在中間件中對(duì)請(qǐng)求參數(shù)進(jìn)行校驗(yàn)。請(qǐng)求參數(shù)的校驗(yàn),在路由函數(shù)中實(shí)現(xiàn)。

在 internal/apiserver/server.go 文件中添加以下代碼創(chuàng)建請(qǐng)求參數(shù)校驗(yàn)實(shí)例,代碼如下:

import (
    ...
    "github.com/onexstack/miniblog/internal/apiserver/pkg/validation"
    ...
)
...
// ServerConfig 包含服務(wù)器的核心依賴和配置.
type ServerConfig struct {
    val *validation.Validator
}
...
// NewServerConfig 創(chuàng)建一個(gè) *ServerConfig 實(shí)例.
// 進(jìn)階:這里其實(shí)可以使用依賴注入的方式,來(lái)創(chuàng)建 *ServerConfig.
func (cfg *Config) NewServerConfig() (*ServerConfig, error) {
    ...
    return &ServerConfig{
        ...
        val: validation.New(store),
    }, nil
}

在創(chuàng)建 HTTP Handler 時(shí),傳入請(qǐng)求參數(shù)校驗(yàn)實(shí)例,代碼如下:

func (c *ServerConfig) InstallRESTAPI(engine *gin.Engine) {
    ...
    // 創(chuàng)建核心業(yè)務(wù)處理器
    handler := handler.NewHandler(c.biz, c.val)
    ...
}

在 HTTP Handler 層的方法中傳入請(qǐng)求參數(shù)校驗(yàn)方法。例如,ListPost 接口 Handler 層代碼實(shí)現(xiàn)如下:

// ListPosts 列出用戶的所有博客帖子.
func (h *Handler) ListPost(c *gin.Context) {
    core.HandleQueryRequest(c, h.biz.PostV1().List, h.val.ValidateListPostRequest)
}

調(diào)用 core.HandleQueryRequest 函數(shù)時(shí),顯式會(huì)傳入校驗(yàn)方法 ValidateListPostRequest。

3. gRPC 請(qǐng)求參數(shù)校驗(yàn)

gRPC 接口的請(qǐng)求參數(shù)校驗(yàn)統(tǒng)一通過(guò) gRPC 攔截器實(shí)現(xiàn)。

在 internal/apiserver/grpcserver.go 文件中,新增以下代碼,用來(lái)在攔截器鏈中添加請(qǐng)求參數(shù)校驗(yàn)攔截器:

import (
    ...
    genericvalidation "github.com/onexstack/onexstack/pkg/validation"
    ...
)
...
func (c *ServerConfig) NewGRPCServerOr() (server.Server, error) {
    serverOptions := []grpc.ServerOption{
        // 注意攔截器順序!
        grpc.ChainUnaryInterceptor(
            ...
            mw.ValidatorInterceptor(genericvalidation.NewValidator(c.val)),
        ),
    }
    ...
}

上述代碼用 genericvalidation.NewValidator 函數(shù)創(chuàng)建通用校驗(yàn)層實(shí)例。創(chuàng)建通用校驗(yàn)層實(shí)例時(shí),會(huì)解析傳入的請(qǐng)求參數(shù)校驗(yàn)實(shí)例 c.val,NewValidator 函數(shù)會(huì)從實(shí)例中提取出所有方法聲明格式為 ValidateXXX(ctx context.Context, rq *apiv1.XXX) error 的方法,并保存在通用校驗(yàn)層的內(nèi)部 registry 中。

ValidatorInterceptor 攔截器實(shí)現(xiàn)如下:

// ValidatorInterceptor 是一個(gè) gRPC 攔截器,用于對(duì)請(qǐng)求進(jìn)行驗(yàn)證.
func ValidatorInterceptor(validator RequestValidator) grpc.UnaryServerInterceptor {
    return func(ctx context.Context, rq any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
        // 調(diào)用自定義驗(yàn)證方法
        if err := validator.Validate(ctx, rq); err != nil {
            // 注意這里不用返回 errno.ErrInvalidArgument 類(lèi)型的錯(cuò)誤信息,由 validator.Validate 返回.
            return nil, err // 返回驗(yàn)證錯(cuò)誤
        }

        // 繼續(xù)處理請(qǐng)求
        return handler(ctx, rq)
    }
}

在 ValidatorInterceptor 攔截器中,會(huì)調(diào)用通用校驗(yàn)層實(shí)例的 Validate 方法,Validate 方法實(shí)現(xiàn)代碼如下所示:

// Validate validates the request using the appropriate validation method.
func (v *Validator) Validate(ctx context.Context, request any) error {
    validationFunc, ok := v.registry[reflect.TypeOf(request).Elem().Name()]
    if !ok {
        return nil // No validation function found for the request type
    }

    result := validationFunc.Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(request)})
    if !result[0].IsNil() {
        return result[0].Interface().(error)
    }

    return nil
}

Validate 方法會(huì)從通用校驗(yàn)層實(shí)例的 registry 中查找鍵為 gRPC 接口請(qǐng)求參數(shù)結(jié)構(gòu)體名稱(例如 LoginRequest)的記錄。如果找到,說(shuō)明該請(qǐng)求參數(shù)結(jié)構(gòu)體已經(jīng)指定了自定義的請(qǐng)求參數(shù)校驗(yàn)方法,執(zhí)行注冊(cè)的校驗(yàn)方法進(jìn)行請(qǐng)求參數(shù)校驗(yàn)。否則,不執(zhí)行校驗(yàn)邏輯。

至此,請(qǐng)求參數(shù)校驗(yàn)代碼開(kāi)發(fā)完成,完整代碼見(jiàn) feature/s22 分支。

四、請(qǐng)求處理測(cè)試

至此,我們已經(jīng)實(shí)現(xiàn)了 miniblog 的核心邏輯。本節(jié)就來(lái)測(cè)試下這些功能是否正常可用。測(cè)試內(nèi)容包括以下幾部分:

  • 接口測(cè)試:測(cè)試健康檢查接口、用戶接口、博客接口是否可以正常工作;
  • 請(qǐng)求處理功能測(cè)試:測(cè)試請(qǐng)求參數(shù)默認(rèn)值設(shè)置、請(qǐng)求參數(shù)校驗(yàn)功能是否可用。

1. 接口測(cè)試

為了方便讀者測(cè)試功能,miniblog 項(xiàng)目已經(jīng)提前編寫(xiě)好了接口測(cè)試代碼。運(yùn)行以下命令來(lái)分別來(lái)測(cè)試健康檢查接口、用戶接口、博客接口。

修改 $HOME/.miniblog/mb-apiserver.yaml 文件,將 server-mode 設(shè)置為 grpc-gateway。

打開(kāi)一個(gè) Linux 終端,運(yùn)行以下命令啟動(dòng) mb-apiserver 服務(wù):

$ make build BINS=mb-apiserver
$ _output/platforms/linux/amd64/mb-apiserver

打開(kāi)另一個(gè) Linux 終端,運(yùn)行以下命令分別測(cè)試健康檢查接口、用戶接口、博客接口:

$ go run examples/client/health/main.go # 測(cè)試健康檢查接口
{"timestamp":"2025-02-01 16:38:08"}
$ go run examples/client/user/main.go # 測(cè)試用戶相關(guān)接口
2025/02/01 16:38:22 [CreateUser     ] Success to create user, userID: user-die7iy
...
2025/02/01 16:38:22 [DeleteUser     ] Success to delete user: user-die7iy
2025/02/01 16:38:22 [All            ] Success to test all user api
$ go run examples/client/post/main.go # 測(cè)試博客相關(guān)接口
2025/02/01 16:38:51 [CreateUser     ] Success to create user, userID: user-die7iy
...
2025/02/01 16:38:51 [All            ] Success to test all post api
2025/02/01 16:38:51 [Login          ] Success to login with root account

運(yùn)行上述測(cè)試代碼,日志輸出中沒(méi)有錯(cuò)誤,說(shuō)明接口功能正常。

2. 請(qǐng)求處理功能測(cè)試

運(yùn)行以下命令測(cè)試請(qǐng)求處理功能是否正常工作:

$ go run examples/client/reqprocess/main.go # 測(cè)試請(qǐng)求處理功能
2025/02/01 16:39:17 [CreateUser     ] Success to create user, userID: user-die7iy
2025/02/01 16:39:17 [Login          ] Success to login
2025/02/01 16:39:17 [GetUser        ] Success in testing request parameter default value setting
2025/02/01 16:39:17 [GetUser        ] Success in testing request parameter validation

五、小結(jié)(AI 自動(dòng)生成并人工審核)

本文詳細(xì)介紹了在 Go 項(xiàng)目開(kāi)發(fā)中如何實(shí)現(xiàn) API 接口請(qǐng)求參數(shù)的校驗(yàn)邏輯,并以 miniblog 項(xiàng)目為例進(jìn)行了實(shí)踐。

文章首先闡述了對(duì)請(qǐng)求參數(shù)進(jìn)行校驗(yàn)的重要性,強(qiáng)調(diào)其在提升系統(tǒng)穩(wěn)定性、確保數(shù)據(jù)合法性、增強(qiáng)用戶體驗(yàn)以及提高代碼可維護(hù)性等方面的作用。隨后,文章分析了常見(jiàn)的參數(shù)校驗(yàn)方法,包括手動(dòng)校驗(yàn)、第三方庫(kù)校驗(yàn)、框架內(nèi)置校驗(yàn)、工具生成校驗(yàn)代碼以及中間件校驗(yàn)等,并指出實(shí)際開(kāi)發(fā)中可能因使用多種校驗(yàn)方式導(dǎo)致的規(guī)范性問(wèn)題。

基于此,miniblog 項(xiàng)目設(shè)計(jì)了一種標(biāo)準(zhǔn)化、靈活且易維護(hù)的參數(shù)校驗(yàn)方案,采用統(tǒng)一的校驗(yàn)接口格式,并通過(guò)通用校驗(yàn)層實(shí)現(xiàn)了復(fù)雜校驗(yàn)邏輯的支持和復(fù)用。

具體實(shí)現(xiàn)中,miniblog 針對(duì) HTTP 和 gRPC 請(qǐng)求分別設(shè)計(jì)了對(duì)應(yīng)的校驗(yàn)機(jī)制,其中 HTTP 請(qǐng)求在路由層實(shí)現(xiàn)校驗(yàn),gRPC 請(qǐng)求則通過(guò)攔截器完成參數(shù)驗(yàn)證。

最后,文章通過(guò)接口測(cè)試和請(qǐng)求處理功能測(cè)試驗(yàn)證了參數(shù)校驗(yàn)方案的正確性與可靠性,為 Go 項(xiàng)目開(kāi)發(fā)提供了實(shí)用的參考。

責(zé)任編輯:趙寧寧 來(lái)源: 令飛編程
相關(guān)推薦

2010-03-17 14:50:50

Fedora Core

2025-05-20 08:20:00

GoGo Context上下文

2022-10-30 23:13:30

contextGo語(yǔ)言

2023-06-08 16:47:09

軟件開(kāi)發(fā)工具

2020-04-22 09:00:00

REST API參數(shù)化前端

2023-10-30 16:14:44

Metrics SD數(shù)據(jù)庫(kù)

2023-10-26 11:07:48

Golang開(kāi)發(fā)

2024-01-11 11:25:22

2021-12-15 09:51:42

Web開(kāi)發(fā)數(shù)據(jù)

2025-07-29 09:06:04

2023-10-27 12:11:33

2022-04-18 10:20:31

數(shù)據(jù)映射工具

2023-05-16 15:25:08

2022-10-20 10:02:16

前端測(cè)試開(kāi)發(fā)

2022-04-20 12:08:17

容器安全漏洞網(wǎng)絡(luò)安全

2019-10-10 09:00:30

云端云遷移云計(jì)算

2023-06-16 08:36:25

多線程編程數(shù)據(jù)競(jìng)爭(zhēng)

2022-11-28 23:48:06

JavaScript編程語(yǔ)言技巧

2024-10-29 20:58:38

2022-11-23 10:49:41

IT資產(chǎn)管理IT戰(zhàn)略
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

日韩porn| 91在线视频免费播放| 精品视频在线观看免费观看 | 一二三不卡视频| 91精品国产66| 亚洲高清久久久| 日本欧洲国产一区二区| 国产美女免费视频| 午夜一区二区三区不卡视频| 在线看福利67194| 亚洲熟女一区二区三区| 日韩经典一区| 午夜激情一区二区三区| 亚洲高清资源综合久久精品| 高h震动喷水双性1v1| 日本成人在线电影网| 久久久久在线观看| 国产一二三四视频| 偷窥自拍亚洲色图精选| 日韩视频免费观看高清完整版 | 男人天堂视频网| 欧美成人一品| 在线观看视频99| 亚洲高清无码久久| 成人精品视频在线观看| 色综合久久综合网欧美综合网| 欧美一级特黄aaaaaa在线看片| 精品乱码一区二区三四区视频| 国产91精品欧美| 国产精品一区二区3区| 97久久久久久久| 午夜精品婷婷| 久久天堂电影网| 日本免费www| 久草成人在线| 日韩成人中文字幕在线观看| 日批视频在线看| 日韩中文视频| 色菇凉天天综合网| 久久精品国产精品亚洲色婷婷| av毛片在线| 中文字幕在线观看不卡视频| 神马欧美一区二区| 国产大片在线免费观看| 久久久综合网站| 久久精品久久精品国产大片| 成人av一区二区三区在线观看| 久久99精品久久只有精品| 国产精品福利网| aaa在线视频| 丝袜亚洲另类欧美综合| 欧洲中文字幕国产精品| 日韩精品一区二区三区国语自制| 欧美视频久久| 欧美福利视频在线观看| 精品欧美一区二区久久久久| 66视频精品| 久久精品电影一区二区| 中文字幕无码日韩专区免费 | 久久综合久久综合九色| 黄色91av| 青青草在线视频免费观看| 91麻豆福利精品推荐| 蜜桃av噜噜一区二区三区| 天天操天天干天天爽| 97久久超碰国产精品| 精品999在线观看| 午夜小视频免费| 91美女片黄在线观看91美女| 久久99精品久久久久子伦| 神马久久精品| 国产日产欧美一区二区三区 | 亚洲午夜久久久久久尤物| 欧美激情欧美狂野欧美精品| 久久精品视频久久| 国产一区二区你懂的| 日本一区二区三区四区视频| 亚洲自拍一区在线观看| 美女网站色91| 99久re热视频这里只有精品6| 亚洲AV无码乱码国产精品牛牛| 国产 日韩 欧美大片| 快播亚洲色图| 免费黄色在线看| 亚洲伊人色欲综合网| 欧美韩国日本在线| 国产精品传媒麻豆hd| 制服丝袜亚洲网站| 黄色片视频免费观看| 国产精品羞羞答答在线观看| 日韩网站免费观看高清| 国产乡下妇女做爰视频| 日韩精品欧美精品| 91成人免费看| 久草福利在线| 一区二区三区日韩精品视频| 黄色片久久久久| 韩国一区二区三区视频| 日韩精品一区二区视频| 国产免费一区二区三区四区| 国产精品嫩草99av在线| 91免费在线视频网站| 欧美黄色小说| 樱花影视一区二区| 蜜臀视频一区二区三区| 一区二区三区视频播放| 亚洲色图综合久久| 久久久久久久久久久97| 另类综合日韩欧美亚洲| 久久99精品久久久久子伦| 欧美激情二区| 色婷婷av一区二区| 国产视频久久久久久| 欧美韩国日本在线观看| 91福利视频网| 亚洲xxx在线| 一区在线观看免费| 中文字幕无码不卡免费视频| 伊人久久影院| 神马久久桃色视频| 一级片在线观看免费| 成人综合婷婷国产精品久久蜜臀| 五月天国产一区| 国产精品xx| 日韩你懂的在线播放| 国产又粗又猛又爽又黄的视频小说 | 欧美性xxx| 亚洲国产精品高清久久久| 好吊日在线视频| 美女脱光内衣内裤视频久久网站 | 中文字幕午夜精品一区二区三区| 国产精品h片在线播放| 熟妇高潮一区二区高潮| 亚洲一级片在线观看| 亚洲黄色片免费看| 色综合五月天| 国产精品久久久久久久美男| 日本一级在线观看| 懂色aⅴ精品一区二区三区蜜月| 亚洲美女高潮久久久| 午夜激情一区| 97超级碰碰| 26uuu亚洲电影在线观看| 欧美日韩www| 国产免费嫩草影院| 美女脱光内衣内裤视频久久影院| 午夜精品美女久久久久av福利| 午夜裸体女人视频网站在线观看| 日韩欧美在线影院| 极品盗摄国产盗摄合集| 国产精一品亚洲二区在线视频| 久久久国产精华液999999| 欧美天堂一区| 久久影院免费观看| 国产91麻豆视频| 午夜影院久久久| 亚洲最大的黄色网| 麻豆精品网站| 日本三级中国三级99人妇网站| 美女网站视频一区| 亚洲区一区二区| 亚洲午夜无码久久久久| 国产精品丝袜一区| 日本精品一区在线| 欧美日韩一区自拍| 精品蜜桃一区二区三区| 一个人www视频在线免费观看| 亚洲色图18p| 国产又粗又猛视频免费| 亚洲欧美日韩中文字幕一区二区三区 | 日韩不卡一二区| 国产精品115| 欧美有码在线观看| av小片在线| 欧美一级片免费看| 日韩经典在线观看| 中文字幕欧美国产| 日韩欧美色视频| 91久久视频| 色狠狠久久av五月综合| 精品国产伦一区二区三区观看说明| 美女国内精品自产拍在线播放| 高h震动喷水双性1v1| 色久优优欧美色久优优| 日韩在线一卡二卡| 99riav一区二区三区| 亚洲五月天综合| 欧美国产激情| 玛丽玛丽电影原版免费观看1977| 99久久婷婷国产综合精品首页| 久久伊人91精品综合网站| 日本一本草久在线中文| 91精品欧美一区二区三区综合在| 日本少妇在线观看| 国产日产亚洲精品系列| 亚洲美女精品视频| 日日骚欧美日韩| 精品国产三级a∨在线| 奇米777国产一区国产二区| 国产精品视频1区| 久草免费在线视频| 久久精品最新地址| 免费在线视频你懂得| 91精品福利在线一区二区三区 | 激情无码人妻又粗又大| 成人av电影在线| 午夜视频在线观| 三级欧美韩日大片在线看| www.亚洲成人网| 欧美激情欧美| 久久精品日韩| 一区二区三区在线资源| 国产主播欧美精品| 亚洲精品中文字幕| 欧美激情伊人电影| 国产盗摄在线观看| 日韩在线观看网址| 黄色美女网站在线观看| 欧美精品一区二区三区高清aⅴ| 国产精品嫩草影院精东| 在线观看视频一区二区| 欧美三级午夜理伦| 亚洲午夜精品久久久久久久久| 免费成人深夜夜行网站| 国产精品欧美精品| 三上悠亚影音先锋| 99天天综合性| 无码成人精品区在线观看| 国产一区久久久| 亚洲高清免费在线观看| 奇米一区二区三区av| 免费在线激情视频| 国产精品亚洲欧美| 中文字幕在线中文| 午夜亚洲福利| 艳母动漫在线免费观看| 国产精品videosex性欧美| 日韩欧美一区二区在线观看 | 99久久久精品| 国产大学生视频| av资源站一区| 波多野结衣视频播放| 成人激情黄色小说| 国产伦理在线观看| 懂色av一区二区夜夜嗨| 无码人妻一区二区三区一| 国产福利一区二区三区视频| 26uuu国产| 成人黄色小视频在线观看| 亚洲成年人av| 99re成人在线| 欧美性xxxx图片| 国产亚洲成年网址在线观看| 白白色免费视频| 国产农村妇女毛片精品久久麻豆| 精品国产成人亚洲午夜福利| 日本一区二区三区国色天香 | jjzzjjzz欧美69巨大| 成人午夜av电影| 日本丰满少妇裸体自慰| 国产亚洲精品精华液| 五月天婷婷丁香网| 一区二区三区中文字幕精品精品 | 波多野结衣中文在线| 国内成人精品一区| 波多野结衣亚洲| 国产精品久久久久久亚洲影视| 欧美激情不卡| 99国产超薄肉色丝袜交足的后果 | 亚洲精品中文字幕乱码三区不卡 | 日韩国产欧美亚洲| 噜噜噜91成人网| 精品亚洲视频在线| 成人一区二区三区| 亚洲AV无码国产成人久久| 中文字幕二三区不卡| 中文字幕av免费在线观看| 五月激情综合色| 波多野结衣视频网址| 欧美精品久久久久久久多人混战| 亚洲成人777777| 亚洲女人天堂成人av在线| 日本三级视频在线观看| 久久久久久久久91| 亚洲成人短视频| 91免费观看| 久久99视频| 真人做人试看60分钟免费| 国产亚洲一区在线| 免费精品99久久国产综合精品应用| 懂色av中文字幕一区二区三区 | 国产成人一区二区| 精品亚洲二区| 老牛影视免费一区二区| 午夜精品毛片| 成年网站在线免费观看| 国内欧美视频一区二区| 无码熟妇人妻av| 一区二区在线观看av| 这里只有精品免费视频| 精品国产一区二区三区久久影院 | www.激情五月| 在线看日韩欧美| 99热99re6国产在线播放| 国产免费一区二区三区香蕉精| 欧美大奶一区二区| 国产一二三四五| 日本在线不卡视频一二三区| av av在线| 亚洲色图19p| 久久这里只有精品9| 精品一区二区电影| 日本在线视频www鲁啊鲁| 国产精品美女呻吟| 亚洲国产网址| 欧美精品自拍视频| 国产精品一二一区| 欧美xxxx精品| 色94色欧美sute亚洲线路二| 男人天堂av网| 久久久久国产一区二区三区| 国产999精品在线观看| 日韩av电影在线观看| 美女国产一区| 日本xxxx裸体xxxx| 亚洲成人高清在线| 欧美自拍偷拍第一页| 久久6免费高清热精品| 日韩欧乱色一区二区三区在线 | 国产亚洲无码精品| 亚洲成a人片在线观看中文| 99在线精品视频免费观看20| 日韩视频永久免费观看| 久久精品97| 一级一片免费播放| 美女视频黄频大全不卡视频在线播放| 一级黄色片大全| 欧美性猛交xxxx| 日本在线一二三| 日本精品在线视频| 啪啪亚洲精品| 蜜臀av午夜一区二区三区| 26uuu国产电影一区二区| 国内自拍视频在线播放| 日韩精品中文在线观看| 欧美极度另类| 欧美日韩免费观看一区| 视频精品一区二区| 在线小视频你懂的| 精品视频1区2区| 嫩草在线视频| 18成人免费观看网站下载| 欧美日韩三区| 国产艳妇疯狂做爰视频| 亚洲成人7777| 丝袜+亚洲+另类+欧美+变态| 日韩美女视频在线观看| 欧洲杯半决赛直播| 亚洲欧美日韩精品一区| 亚洲免费观看高清完整版在线 | 精品国产美女在线| av一级久久| 日韩精品在线观看av| 波多野结衣在线一区| 999视频在线| 久久躁狠狠躁夜夜爽| 2023国产精华国产精品| 中文字幕日本最新乱码视频| 欧美经典一区二区三区| 国产熟女精品视频| 久久久免费电影| 国产精品午夜一区二区三区| 日日干夜夜操s8| 亚洲一区二区三区免费视频| 男人的天堂在线免费视频| 国产精品私拍pans大尺度在线| 欧美激情综合色综合啪啪| 玖玖爱在线观看| 欧美狂野另类xxxxoooo| 爱福利在线视频| 亚洲三区在线| 成人免费视频免费观看| 99久久久无码国产精品免费蜜柚 | 小视频免费在线观看| 深夜福利成人| 成人av高清在线| 亚洲天堂手机版| 68精品久久久久久欧美| 久久影院100000精品| 国产激情视频网站| 欧美精品tushy高清| 亚洲v.com| 四虎4hu永久免费入口| 久久综合色婷婷| 精品乱子伦一区二区| 国产精欧美一区二区三区| 欧美另类综合| 国产又黄又粗视频| 亚洲高清久久久久久| 爱情电影网av一区二区|