日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

gin context和官方context_Go Web 小技巧(一)简化Gin接口代码

發布時間:2023/11/30 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 gin context和官方context_Go Web 小技巧(一)简化Gin接口代码 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

不知道大家在使用 Gin 構建 API 服務時有沒有這樣的問題:

  • 參數綁定的環節可不可以自動處理?
  • 錯誤可不可以直接返回,不想寫空 return, 漏寫就是 bug
  • 本文通過簡單地封裝,利用 go 的接口特性,提供一個解決上述兩個問題的思路

    一、解決過程

    1.1 剛開始時寫 API 服務時

    我們剛開始使用 Gin 寫 API 服務時,一般會按照官方文檔上的 這么寫

    // User 用戶結構 type User struct {UserName string }// CreateUser 創建用戶 func CreateUser(ctx *gin.Context) {var params Userif err := ctx.ShouldBind(&params); err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"code": 400,"msg": "參數錯誤",})logrus.Errorf("params err, %v", params)return}// 一些其他的業務邏輯 ...ctx.JSON(http.StatusOK, gin.H{"code": 0,"msg": "創建成功",}) }func main() {r := gin.Default()r.POST("user", CreateUser)if err := r.Run(":8080"); err != nil {logrus.Fatalf("can not start serve: %v", err)} }

    1.2 封裝返回值

    我們寫了一段時間之后,會發現,我們的返回值的結構是固定的,為什么不抽象一下呢,所以我們創建了一個結構體 Resp ,并且封裝了兩個方法用于成功和失敗這兩種狀態的返回

    // resp.go// Resp 返回 type Resp struct {Code intMsg stringData interface{} }// ErrorResp 錯誤返回值 func ErrorResp(ctx *gin.Context, code int, msg string, data ...interface{}) {resp(ctx, code, msg, data...) }// SuccessResp 正確返回值 func SuccessResp(ctx *gin.Context, msg string, data ...interface{}) {resp(ctx, 0, msg, data...) }// resp 返回 func resp(ctx *gin.Context, code int, msg string, data ...interface{}) {resp := Resp{Code: code,Msg: msg,Data: data,}if len(data) == 1 {resp.Data = data[0]}ctx.JSON(http.StatusOK, resp) }

    添加這個方法之后,我們再看一下 CreateUser 這個方法,成功的從 16 行變到了 12 行

    // main.go // CreateUser 創建用戶 func CreateUser(ctx *gin.Context) {var params Userif err := ctx.ShouldBind(&params); err != nil {ErrorResp(ctx, 400, "參數錯誤")logrus.Errorf("params err, %v", params)return}// 一些其他的業務邏輯 ...SuccessResp(ctx, "創建成功") }

    1.3 兩個痛點

    上面的方法還不夠完整,我們還是有許多重復的邏輯,可以發現我們在寫的絕大多數 API 大概都是這樣:

  • 參數綁定 & 校驗
  • 業務邏輯
  • 返回
  • 這里面有兩個痛點:

  • 參數綁定的環節可不可以自動處理?
  • 錯誤可不可以直接返回,不想寫空 return, 漏寫就是 bug
  • // 不想寫大量這種重復的代碼var params Userif err := ctx.ShouldBind(&params); err != nil {// 下面這三行是不是可以合并成一行ErrorResp(ctx, 400, "參數錯誤")logrus.Errorf("params err, %v", params)return}

    1.4 使用接口封裝請求

    上面的這兩個痛點我們可以通過一個輔助函數解決

    // Requester 請求 type Requester interface {Request(ctx *gin.Context) (*Resp, error) }// Handle 請求 func Handle(r Requester) gin.HandlerFunc {return func(ctx *gin.Context) {resp, err := request(r, ctx)if err != nil {var code *errcode.Errorif !errors.As(err, &code) {code = errcode.Unknown.Wrap(err)}resp = &Resp{Code: code.Code,Msg: code.String(),}_ = ctx.Error(err)}ctx.JSON(http.StatusOK, resp)} }func request(r Requester, ctx *gin.Context) (*controller.Resp, error) {// 參數綁定if err := ctx.ShouldBind(r); err != nil {return nil, errcode.ErrParams.Wrap(err)}return r.Request(ctx) }

    這樣我們只需要實現這個 Requester, 寫 API 時只需要關注業務邏輯就可以了

    // CreateUser 創建用戶 type CreateUser struct {UserName string }func (u *User) Request(ctx *gin.Context) (*Resp, error) {// 業務邏輯// 返回成功值 }func main() {r := gin.Default()r.POST("user", Handle(&CreateUser))if err := r.Run(":8080"); err != nil {logrus.Fatalf("can not start serve: %v", err)} }

    上面的代碼有一個 bug 不知道大家發現沒有,我們上一次請求的參數會被帶到下一次請求當中

    // Handle 請求 func Handle(r Requester) gin.HandlerFunc {return func(ctx *gin.Context) {// 創建一個新的 Requester, 避免將上一次的參數帶到下一次當中if reflect.TypeOf(r).Kind() != reflect.Ptr {panic("must be a pointer")}req := reflect.New(reflect.ValueOf(r).Elem().Type()).Interface().(Requester)resp, err := request(req, ctx)if err != nil {var code *errcode.Errorif !errors.As(err, &code) {code = errcode.Unknown.Wrap(err)}resp = &Resp{Code: code.Code,Msg: code.String(),}_ = ctx.Error(err)}ctx.JSON(http.StatusOK, resp)} }

    二、總結

    大概這樣差不多就 ok 了,還有很多可以完善的點,這里有一些思路,有的已經做了,有的還在路上

  • 每次注冊都寫 Handle(&CreateUser) 還是有點麻煩?
  • 可以封裝一下 gin.IRouter 這個接口,這樣注冊接口就可以和原來一樣了

    2. 參數綁定如果我需要多次綁定怎么辦?

    可以添加一個接口,如果實現了這個接口就執行以下,對于有特殊的參數校驗之類的也可以采用類似的方式處理 type Binder interface {Bind(ctx *gin.Context) error}func request(r Requester, ctx *gin.Context) (*controller.Resp, error) {// 參數綁定if err := ctx.ShouldBind(r); err != nil {return nil, errcode.ErrParams.Wrap(err)}// 其余參數綁定if b, ok := r.(Binder); ok {if err := b.Bind(api); err != nil {return nil, errcode.ErrParams.Wrap(err)}}return r.Request(ctx)}

    3. 怎么輸出 API 文檔?

    可以和 swagger 之類的 API 文檔結合, 利用 go generate 自動生成,順便可以連接口注冊都不用了,添加一行注釋,自動注冊接口,并且輸出接口文檔

    // @Router put /api/v1/userfunc(u *User) Request(ctx *gin.Context) (*Resp, error)

    4. 能不能減少 CURD 代碼?

    可以實現,只需要采用約定的項目接口,可以 利用 go generate 直接自動生成簡單的 CURD 代碼

    博客原文

    Go Web 小技巧(一)簡化Gin接口代碼?lailin.xyz

    總結

    以上是生活随笔為你收集整理的gin context和官方context_Go Web 小技巧(一)简化Gin接口代码的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。