gin context和官方context_Go Web 小技巧(一)简化Gin接口代码
不知道大家在使用 Gin 構建 API 服務時有沒有這樣的問題:
本文通過簡單地封裝,利用 go 的接口特性,提供一個解決上述兩個問題的思路
一、解決過程
1.1 剛開始時寫 API 服務時
我們剛開始使用 Gin 寫 API 服務時,一般會按照官方文檔上的 這么寫
// User 用戶結構 type User struct {UserName string }// CreateUser 創建用戶 func CreateUser(ctx *gin.Context) {var params Userif err := ctx.ShouldBind(¶ms); 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(¶ms); err != nil {ErrorResp(ctx, 400, "參數錯誤")logrus.Errorf("params err, %v", params)return}// 一些其他的業務邏輯 ...SuccessResp(ctx, "創建成功") }1.3 兩個痛點
上面的方法還不夠完整,我們還是有許多重復的邏輯,可以發現我們在寫的絕大多數 API 大概都是這樣:
這里面有兩個痛點:
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 了,還有很多可以完善的點,這里有一些思路,有的已經做了,有的還在路上
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接口代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谁在说谎剧情介绍
- 下一篇: 丁香园 武汉 神童_杭州、武汉、成都哪个