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

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

  1. 參數綁定的環節可不可以自動處理?
  2. 錯誤可不可以直接返回,不想寫空 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 大概都是這樣:

  1. 參數綁定 & 校驗
  2. 業務邏輯
  3. 返回

這里面有兩個痛點:

  1. 參數綁定的環節可不可以自動處理?
  2. 錯誤可不可以直接返回,不想寫空 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 了,還有很多可以完善的點,這里有一些思路,有的已經做了,有的還在路上

  1. 每次注冊都寫 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
d0547c9ca5f4d738fc029874d8a091a6.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/385990.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/385990.shtml
英文地址,請注明出處:http://en.pswp.cn/news/385990.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

7年老Android一次操蛋的面試經歷,深度好文

Java基礎 Java Object類方法HashMap原理,Hash沖突,并發集合,線程安全集合及實現原理HashMap 和 HashTable 區別HashCode 作用,如何重載hashCode方法ArrayList與LinkList區別與聯系GC機制Java反射機制,Java代理模式Jav…

Hadoop大數據應用生態圈中最主要的組件及其關系

Hadoop Common Hadoop Common是在Hadoop0.2版本之后分離出來的HDFS和MapReduce獨立子項目的內容,是Hadoop的核心部分,能為其他模塊提供一些常用工具集,如序列化機制、Hadoop抽象文件系統FileSystem、系統配置工具Configuration,并…

7年老Android一次操蛋的面試經歷,系列教學

公司的需求 不同的公司,不同的需求現在的市場上,公司很多,大致上可以歸納為兩個大類:大公司和小公司,他們招聘時對人才的需求也不一樣。 小公司 小公司他們一般急需的是能夠投入工作的人才,因為公司規模…

丁香園 武漢 神童_杭州、武漢、成都哪個城市更適合程序員發展

很多朋友討論起房價和職業發展機會,都會提到這三個城市,有的人認為目前杭州房價太貴了,生活成本高,華中的武漢和西部崛起的成都都在鼓勵高新技術發展并且有了一定成果,在選擇職業發展和定居城市之間該如何取舍呢&#…

Windows 7 64位系統上搭建Hadoop偽分布式環境(很詳細)

在開始配置前,我們先了解Hadoop的三種運行模式。 Hadoop的三種運行模式 獨立(或本地)模式:無需運行任何守護進程,所有程序都在同一個JVM上執行。在獨立模式下測試和調試MapReduce程序很方便,因此該模式在…

7年老Android一次操蛋的面試經歷,講的太透徹了

由于涉及到的面試題較多導致篇幅較長,我根據這些面試題所涉及到的常問范圍總結了并做出了一份學習進階路線圖???????及面試題答案免費分享給大家,文末有免費領取方式! View面試專題 View的滑動方式View的事件分發機制View的加載流程…

處理效應模型stata實例_stata︱政策處理效應模型sata基本命令匯總

本文來源經管之家論壇,由壇友cuifengbao歸納 Use ,文件名.dta,clear Ssc installpamatch2,replace 一、首先做一元回歸 reg 結果變量 處理變量,r 二、直接引入協變量,再做多元回歸 reg 結果變量 處理變量 協變量1 協變量2 協變量3……,r 三、接下來進行傾向得分匹配 1.將數…

80后程序員月薪30K+感慨中年危機,面試必問!

說說程序猿行業 現在社會上給IT行業貼上了幾個標簽:高薪、高危、高大上、禿頂(哈哈)。這些標簽我相比大家都比較清楚,至于為什么是這些標簽呢?而且這些標簽是真實還是假象呢? 高薪 作為IT行業來說&#…

華為照片在哪個文件夾_原來華為手機還能這樣清理垃圾,怪不得你的手機可以多用5年...

對于目前市場上的智能手機來說,大家的手機功能都是差不多的,除了一些外觀上的差別之外,最大的區別就是手機的內存,但是很多朋友卻表示手機內存很大,但是沒用多久,手機就會出現卡頓或者是運行速度變慢的現象…

996頁阿里Android面試真題解析火爆全網,全網首發!

在安卓系統中: 當系統內存不足時,Android系統將根據進程的優先級選擇殺死一 些不太重要的進程,優先級低的先殺死。進程優先級從高到低如下。 前臺進程 處于正在與用戶交互的activity與前臺activity綁定的service調用了startForeground&…

python不適合大型項目_在大型項目上,Python 是個爛語言嗎? |

【洪強寧的回答(89票)】:太多硬傷和臆想,懶得批。只說“代碼超過 10w 以后你就別想用 python 開發了”這一句,2012年4月豆瓣主站項目代碼行數就近50萬行了,可我們還在用 python 開發。【劉鑫的回答(42票)】:我寫過幾年Python,也寫…

996頁阿里Android面試真題解析火爆全網,分享面經!

導語 學歷永遠是橫在我們進人大廠的一道門檻,好像無論怎么努力,總能被那些985,211 按在地上摩擦! 不僅要被“他們”看不起,在HR挑選簡歷,學歷這塊就直接被刷下去了,連證明自己的機會也沒有,學…

access ole 對象 最大長度_Redis 數據結構和對象系統,有這 12 張圖就夠了!

作者 | 程序員歷小冰責編 | 林瑟Redis 是一個開源的 key-value 存儲系統,它使用六種底層數據結構構建了包含字符串對象、列表對象、哈希對象、集合對象和有序集合對象的對象系統。 今天我們就通過 12 張圖來全面了解一下它的數據結構和對象系統的實現原理。01數據結…

python煙花表白_python炫酷煙花表白源代碼

詳細內容天天敲代碼的朋友,有沒有想過代碼也可以變得很酷炫又浪漫?今天就教大家用Python模擬出綻放的煙花,工作之余也可以隨時讓程序為自己放一場煙花秀。python炫酷煙花表白源代碼這個有趣的小項目并不復雜,只需一點可視化技巧&a…

【面試總結】2021Java春招面試經歷

三、堆空間 基本描述 JVM啟動時創建堆區,是內存管理的核心區,通常情況下也是最大的內存空間,是被所有線程共享的,幾乎所有的對象實例都要在堆中分配內存,所以這里也是垃圾回收的重點空間。 堆棧關系 棧是JVM運行時的…

tableau地圖城市數據_Tableau 地圖 | 無法識別的城市

Tableau自帶的地圖功能很強大,也很簡單只要雙擊具有地理位置角色的字段,即可生成地圖不過有的時候在你部署地圖的時候總會發現有些城市或地名無法識別,提示如下:這篇post就來簡單聊聊為啥今天直說處理方法,不談后臺原理…

【高級Java架構師系統學習】最新Java高級面試題匯

性能調優 影響MySQLServer 性能的相關因素 商業需求對性能的影響系統架構及實現對性能的影響Query語句對系統性能的影響Schema設計對系統的性能影響硬件環境對系統性能的影響 MySQL 數據庫鎖定機制 MySQL鎖定機制簡介各種鎖定機制分析合理利用鎖機制優化MySQL MySQL數據庫Qu…

vue 安裝指定版本swiper_Vue中的runtime-only和runtime-compiler

在我們使用vue-cli的時候,會提示你安裝的版本可以看到有兩種版本:Routime Only和Runtime Compiler版本1.Runtime Only - 代碼中不可以有任何template 性能更高在該版本下,通常需要借助如webpack的vue-loader發工具把.vue文件編譯成js因為是在…

一文搞懂JVM架構:入職3個月的Java程序員面臨轉正

Java基礎 1.JAVA 中的幾種數據類型是什么,各自占用多少字節。 2.String 類能被繼承嗎,為什么。 3. 兩個對象的 hashCode() 相同,則 equals() 也一定為 true,對嗎? 4. String 屬于基礎的數據類型嗎? 5.…

不顯示調用super_讓不懂編程的人愛上iPhone開發(2017秋iOS11+Swift4+Xcode9版)-第11篇

歡迎回到我們的iPhone開發教程系列,讓我們繼續前進吧。重新來過別害怕,哥不是讓你拋棄之前所有的源代碼,從零開始重新構建這個項目!這里說的是游戲界面里面的“Start over”按鈕。在我們的to-do清單里面曾經提到過,這個…