<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
基於現在微服務或者服務化的思想,我們大部分的業務邏輯處理常式都是長這樣的:
比如grpc伺服器端:
func (s *Service) GetUserInfo(ctx context.Context, req *pb.GetUserInfoReq) (*pb.GetUserInfoRsp, error) { // 業務邏輯 // ... }
grpc使用者端:
func (s *Service) GetUserInfo(ctx context.Context, req *pb.GetUserInfoReq, opts ...grpc.CallOption) (*pb.GetUserInfoRsp, error) { // 業務邏輯 // ... }
有些服務我們需要把它包裝為RESTful形式的介面,一般需要經歷以下步驟:
可以發現,引數繫結、處理響應幾乎都是一樣模板程式碼,鑑權也基本上是模板程式碼(當然有些鑑權可能比較複雜)。
而Ginrest庫就是為了消除這些模板程式碼,它不是一個複雜的框架,只是一個簡單的庫,輔助處理這些重複的事情,為了實現這個能力使用了Go1.18的泛型。
這個庫提供以下特性:
範例程式碼在:github.com/jiaxwu/ginr…
首先我們實現兩個簡單的服務:
const ( ErrCodeUserNotExists = 40100 // 使用者不存在 ) type GetUserInfoReq struct { UID int `json:"uid"` } type GetUserInfoRsp struct { UID int `json:"uid"` Username string `json:"username"` Age int `json:"age"` } func GetUserInfo(ctx context.Context, req *GetUserInfoReq) (*GetUserInfoRsp, error) { if req.UID != 10 { return nil, ginrest.NewError(ErrCodeUserNotExists, "user not exists") } return &GetUserInfoRsp{ UID: req.UID, Username: "user_10", Age: 10, }, nil } type UpdateUserInfoReq struct { UID int `json:"uid"` Username string `json:"username"` Age int `json:"age"` } type UpdateUserInfoRsp struct{} func UpdateUserInfo(ctx context.Context, req *UpdateUserInfoReq) (*UpdateUserInfoRsp, error) { if req.UID != 10 { return nil, ginrest.NewError(ErrCodeUserNotExists, "user not exists") } return &UpdateUserInfoRsp{}, nil }
然後使用Gin+Ginrest包裝為RESTful介面:
可以看到Register()裡面每個介面都只需要一行程式碼!
func main() { e := gin.New() e.Use(ginrest.Recovery()) Register(e) if err := e.Run("127.0.0.1:8000"); err != nil { log.Println(err) } } // 註冊請求 func Register(e *gin.Engine) { // 簡單請求,不需要認證 e.GET("/user/info/get", ginrest.Do(nil, GetUserInfo)) // 認證,繫結UID,處理 reqFunc := func(c *gin.Context, req *UpdateUserInfoReq) { req.UID = GetUID(c) } // 這裡拆多一步是為了顯示第一個引數是ReqFunc e.POST("/user/info/update", Verify, ginrest.Do(reqFunc, UpdateUserInfo)) } const ( KeyUserID = "KeyUserID" ) // 簡單包裝方便使用 func GetUID(c *gin.Context) int { return ginrest.GetKey[int](c, KeyUserID) } // 簡單包裝方便使用 func SetUID(c *gin.Context, uid int) { ginrest.SetKey(c, KeyUserID, uid) } // 認證 func Verify(c *gin.Context) { // 認證處理 // ... // 忽略認證的具體邏輯 SetUID(c, 10) }
執行上面程式碼,然後嘗試存取介面,可以看到返回結果:
請求1
GET http://127.0.0.1:8000/user/info/get
{
"uid": 10
}
響應1
{
"code": 0,
"msg": "ok",
"data": {
"uid": 10,
"username": "user_10",
"age": 10
}
}
請求2
GET http://127.0.0.1:8000/user/info/get
{
"uid": 1
}
響應2
{
"code": 40100,
"msg": "user not exists"
}
請求3
POST http://127.0.0.1:8000/user/info/update
{
"username": "jiaxwu",
"age": 10
}
響應3
{
"code": 0,
"msg": "ok",
"data": {}
}
Do()和DoOpt()都會轉發到do(),它其實是一個模板函數,把髒活累活給處理了:
// 處理請求 func do[Req any, Rsp any, Opt any](reqFunc ReqFunc[Req], serviceFunc ServiceFunc[Req, Rsp], serviceOptFunc ServiceOptFunc[Req, Rsp, Opt], opts ...Opt) gin.HandlerFunc { return func(c *gin.Context) { // 引數繫結 req, err := BindJSON[Req](c) if err != nil { return } // 進一步處理請求結構體 if reqFunc != nil { reqFunc(c, req) } var rsp *Rsp // 業務邏輯函數呼叫 if serviceFunc != nil { rsp, err = serviceFunc(c, req) } else if serviceOptFunc != nil { rsp, err = serviceOptFunc(c, req, opts...) } else { panic("must one of ServiceFunc and ServiceFuncOpt") } // 處理響應 ProcessRsp(c, rsp, err) } }
用於把一個標準服務封裝為一個RESTfulgin.HandlerFunc
,對應Do()、DoOpt()函數。
DoOpt()相比於Do()多了一個opts引數,因為很多rpc框架使用者端都有一個opts引數作為結尾。
還有一個BindJSON()
,用於把請求體包裝為一個Req結構體:
// 引數繫結 func BindJSON[T any](c *gin.Context) (*T, error) { var req T if err := c.ShouldBindJSON(&req); err != nil { FailureCodeMsg(c, ErrCodeInvalidReq, "invalid param") return nil, err } return &req, nil }
如果無法使用Do()和DoOpt()則可以使用此方法。
用於把rsp、error、errcode、errmsg等資料封裝為一個JSON格式響應體,對應ProcessRsp()、Success()、Failure()、FailureCodeMsg()函數。
比如ProcessRsp()
需要帶上rsp和error,這樣業務裡面就不需要再寫如下模板程式碼了:
// 處理簡單響應 func ProcessRsp(c *gin.Context, rsp any, err error) { if err != nil { Failure(c, err) return } Success(c, rsp) }
響應格式統一為:
// 響應 type Rsp struct { Code int `json:"code"` Msg string `json:"msg"` Data any `json:"data,omitempty"` }
Success()
用於處理成功情況:
// 請求成功 func Success(c *gin.Context, data any) { ginRsp(c, http.StatusOK, &Rsp{ Code: ErrCodeOK, Msg: "ok", Data: data, }) }
其餘同理。
如果無法使用Do()和DoOpt()則可以使用這些方法。
一般我們都需要在出錯時帶上一個業務錯誤碼,方便使用者端處理。因此我們需要提供一個合適的error型別:
// 錯誤 type Error struct { Code int `json:"code"` Msg string `json:"msg"` }
我們提供了一些函數方便使用Error
,對應NewError()、ToError()、ErrCode()、ErrMsg()、ErrEqual()函數。
比如NewError()
生成一個Error型別error:
// 通過code和msg產生一個錯誤 func NewError(code int, msg string) error { return &Error{ Code: code, Msg: msg, } }
Gin的請求是鏈式處理的,也就是多個handler順序的處理一個請求,比如:
reqFunc := func(c *gin.Context, req *UpdateUserInfoReq) { req.UID = ginrest.GetKey[int](c, KeyUserID) } // 認證,繫結UID,處理 e.POST("/user/info/update", Verify, ginrest.Do(reqFunc, UpdateUserInfo))
這個介面經歷了Verify和ginrest.Do兩個handler,其中我們在Verify的時候通過認證知道了使用者的身份資訊(比如uid),我們希望把這個uid存起來,這樣可以在業務邏輯裡使用。
因此我們提供了SetKey()、GetKey()兩個函數,用於儲存請求上下文:
比如認證通過後我們可以設定UID到上下文,然後在reqFunc()裡讀取設定到req裡面(下面介紹)。
// 認證 func Verify(c *gin.Context) { // 認證處理 // ... // 忽略認證的具體邏輯 ginrest.SetKey(c, KeyUserID, uid) }
上面我們設定了請求上下文,比如UID,但是其實我們並不知道具體這個UID是需要設定到req裡的哪個欄位,因此我們提供了一個回撥函數ReqFunc(),用於設定Req:
// 這裡↓ reqFunc := func(c *gin.Context, req *UpdateUserInfoReq) { req.UID = ginrest.GetKey[int](c, KeyUserID) } // 認證,繫結UID,處理 e.POST("/user/info/update", Verify, ginrest.Do(reqFunc, UpdateUserInfo))
如果這個庫的設計不符合具體的業務,也可以按照這種思路去封裝一個類似的庫,只要儘可能的統一請求、響應的格式,就可以減少很多重複的模板程式碼。
以上就是Go Ginrest實現一個RESTful介面的詳細內容,更多關於Go Ginrest實現RESTful介面的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45