背景
基于現(xiàn)在微服務(wù)或者服務(wù)化的思想,我們大部分的業(yè)務(wù)邏輯處理函數(shù)都是長(zhǎng)這樣的:
比如grpc服務(wù)端:
grpc客戶端:
有些服務(wù)我們需要把它包裝為RESTful形式的接口,一般需要經(jīng)歷以下步驟:
指定HTTP方法、URL
鑒權(quán)
參數(shù)綁定
處理請(qǐng)求
處理響應(yīng)
可以發(fā)現(xiàn),參數(shù)綁定、處理響應(yīng)幾乎都是一樣模板代碼,鑒權(quán)也基本上是模板代碼(當(dāng)然有些鑒權(quán)可能比較復(fù)雜)。
而Ginrest庫(kù)就是為了消除這些模板代碼,它不是一個(gè)復(fù)雜的框架,只是一個(gè)簡(jiǎn)單的庫(kù),輔助處理這些重復(fù)的事情,為了實(shí)現(xiàn)這個(gè)能力使用了Go1.18的泛型。
特性
這個(gè)庫(kù)提供以下特性:
封裝RESTful請(qǐng)求響應(yīng)
封裝RESTful請(qǐng)求為標(biāo)準(zhǔn)格式服務(wù)
封裝標(biāo)準(zhǔn)格式服務(wù)處理結(jié)果為標(biāo)準(zhǔn)RESTful響應(yīng)格式:Rsp{code, msg, data}
默認(rèn)使用統(tǒng)一數(shù)字錯(cuò)誤碼格式:[0, 4XXXX, 5XXXX]
默認(rèn)使用標(biāo)準(zhǔn)錯(cuò)誤格式:Error{code, msg}
默認(rèn)統(tǒng)一狀態(tài)碼[200, 400, 500]
提供Recovery中間件,統(tǒng)一panic時(shí)的響應(yīng)格式
提供SetKey()、GetKey()方法,用于存儲(chǔ)請(qǐng)求上下文(泛型)
提供ReqFunc(),用于設(shè)置Req(泛型)
使用例子
首先我們實(shí)現(xiàn)兩個(gè)簡(jiǎn)單的服務(wù):
然后使用Gin+Ginrest包裝為RESTful接口:
可以看到Register()里面每個(gè)接口都只需要一行代碼!
運(yùn)行上面代碼,然后嘗試訪問(wèn)接口,可以看到返回結(jié)果:
實(shí)現(xiàn)原理
Do()和DoOpt()都會(huì)轉(zhuǎn)發(fā)到do(),它其實(shí)是一個(gè)模板函數(shù),把臟活累活給處理了:
功能列表
處理請(qǐng)求
用于把一個(gè)標(biāo)準(zhǔn)服務(wù)封裝為一個(gè)RESTfulgin.HandlerFunc,對(duì)應(yīng)Do()、DoOpt()函數(shù)。
DoOpt()相比于Do()多了一個(gè)opts參數(shù),因?yàn)楹芏鄏pc框架客戶端都有一個(gè)opts參數(shù)作為結(jié)尾。
還有一個(gè)BindJSON(),用于把請(qǐng)求體包裝為一個(gè)Req結(jié)構(gòu)體:
如果無(wú)法使用Do()和DoOpt()則可以使用此方法。
處理響應(yīng)
用于把rsp、error、errcode、errmsg等數(shù)據(jù)封裝為一個(gè)JSON格式響應(yīng)體,對(duì)應(yīng)ProcessRsp()、Success()、Failure()、FailureCodeMsg()函數(shù)。
比如ProcessRsp()需要帶上rsp和error,這樣業(yè)務(wù)里面就不需要再寫如下模板代碼了:
響應(yīng)格式統(tǒng)一為:
Success()用于處理成功情況:
其余同理。
如果無(wú)法使用Do()和DoOpt()則可以使用這些方法。
處理錯(cuò)誤
一般我們都需要在出錯(cuò)時(shí)帶上一個(gè)業(yè)務(wù)錯(cuò)誤碼,方便客戶端處理。因此我們需要提供一個(gè)合適的error類型:
我們提供了一些函數(shù)方便使用Error,對(duì)應(yīng)NewError()、ToError()、ErrCode()、ErrMsg()、ErrEqual()函數(shù)。
比如NewError()生成一個(gè)Error類型error:
請(qǐng)求上下文操作
Gin的請(qǐng)求是鏈?zhǔn)教幚淼模簿褪嵌鄠€(gè)handler順序的處理一個(gè)請(qǐng)求,比如:
這個(gè)接口經(jīng)歷了Verify和ginrest.Do兩個(gè)handler,其中我們?cè)赩erify的時(shí)候通過(guò)認(rèn)證知道了用戶的身份信息(比如uid),我們希望把這個(gè)uid存起來(lái),這樣可以在業(yè)務(wù)邏輯里使用。
因此我們提供了SetKey()、GetKey()兩個(gè)函數(shù),用于存儲(chǔ)請(qǐng)求上下文:
比如認(rèn)證通過(guò)后我們可以設(shè)置UID到上下文,然后在reqFunc()里讀取設(shè)置到req里面(下面介紹)。
請(qǐng)求結(jié)構(gòu)體處理
上面我們?cè)O(shè)置了請(qǐng)求上下文,比如UID,但是其實(shí)我們并不知道具體這個(gè)UID是需要設(shè)置到req里的哪個(gè)字段,因此我們提供了一個(gè)回調(diào)函數(shù)ReqFunc(),用于設(shè)置Req:
注
如果這個(gè)庫(kù)的設(shè)計(jì)不符合具體的業(yè)務(wù),也可以按照這種思路去封裝一個(gè)類似的庫(kù),只要盡可能的統(tǒng)一請(qǐng)求、響應(yīng)的格式,就可以減少很多重復(fù)的模板代碼。