最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go?Ginrest實(shí)現(xiàn)一個(gè)RESTful接口

 更新時(shí)間:2022年08月18日 09:56:15   作者:jiaxwu  
這篇文章主要為大家介紹了Go?Ginrest實(shí)現(xiàn)一個(gè)RESTful接口示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

背景

基于現(xiàn)在微服務(wù)或者服務(wù)化的思想,我們大部分的業(yè)務(wù)邏輯處理函數(shù)都是長(zhǎng)這樣的:

比如grpc服務(wù)端:

func (s *Service) GetUserInfo(ctx context.Context, req *pb.GetUserInfoReq) (*pb.GetUserInfoRsp, error) {
    // 業(yè)務(wù)邏輯
    // ...
}

grpc客戶端:

func (s *Service) GetUserInfo(ctx context.Context, req *pb.GetUserInfoReq, opts ...grpc.CallOption) (*pb.GetUserInfoRsp, error) {
    // 業(yè)務(wù)邏輯
    // ...
}

有些服務(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的泛型。

倉(cāng)庫(kù)地址:github.com/jiaxwu/ginr…

特性

這個(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(泛型)

使用例子

示例代碼在:github.com/jiaxwu/ginr…

首先我們實(shí)現(xiàn)兩個(gè)簡(jiǎn)單的服務(wù):

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()里面每個(gè)接口都只需要一行代碼!

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)
	}
}
// 注冊(cè)請(qǐng)求
func Register(e *gin.Engine) {
	// 簡(jiǎn)單請(qǐng)求,不需要認(rèn)證
	e.GET("/user/info/get", ginrest.Do(nil, GetUserInfo))
	// 認(rèn)證,綁定UID,處理
        reqFunc := func(c *gin.Context, req *UpdateUserInfoReq) {
		req.UID = GetUID(c)
	} // 這里拆多一步是為了顯示第一個(gè)參數(shù)是ReqFunc
	e.POST("/user/info/update", Verify, ginrest.Do(reqFunc, UpdateUserInfo))
}
const (
	KeyUserID = "KeyUserID"
)
// 簡(jiǎn)單包裝方便使用
func GetUID(c *gin.Context) int {
	return ginrest.GetKey[int](c, KeyUserID)
}
// 簡(jiǎn)單包裝方便使用
func SetUID(c *gin.Context, uid int) {
	ginrest.SetKey(c, KeyUserID, uid)
}
// 認(rèn)證
func Verify(c *gin.Context) {
	// 認(rèn)證處理
	// ...
        // 忽略認(rèn)證的具體邏輯
	SetUID(c, 10)
}

運(yùn)行上面代碼,然后嘗試訪問(wèn)接口,可以看到返回結(jié)果:

請(qǐng)求1
GET http://127.0.0.1:8000/user/info/get
{
    "uid": 10
}
響應(yīng)1
{
    "code": 0,
    "msg": "ok",
    "data": {
        "uid": 10,
        "username": "user_10",
        "age": 10
    }
}
請(qǐng)求2
GET http://127.0.0.1:8000/user/info/get
{
    "uid": 1
}
響應(yīng)2
{
    "code": 40100,
    "msg": "user not exists"
}
請(qǐng)求3
POST http://127.0.0.1:8000/user/info/update
{
    "username": "jiaxwu",
    "age": 10
}
響應(yīng)3
{
    "code": 0,
    "msg": "ok",
    "data": {}
}

實(shí)現(xiàn)原理

Do()和DoOpt()都會(huì)轉(zhuǎn)發(fā)到do(),它其實(shí)是一個(gè)模板函數(shù),把臟活累活給處理了:

// 處理請(qǐng)求
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) {
		// 參數(shù)綁定
		req, err := BindJSON[Req](c)
		if err != nil {
			return
		}
		// 進(jìn)一步處理請(qǐng)求結(jié)構(gòu)體
		if reqFunc != nil {
			reqFunc(c, req)
		}
		var rsp *Rsp
		// 業(yè)務(wù)邏輯函數(shù)調(diào)用
		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")
		}
		// 處理響應(yīng)
		ProcessRsp(c, rsp, err)
	}
}

功能列表

處理請(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)體:

// 參數(shù)綁定
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
}

如果無(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ù)里面就不需要再寫如下模板代碼了:

// 處理簡(jiǎn)單響應(yīng)
func ProcessRsp(c *gin.Context, rsp any, err error) {
	if err != nil {
		Failure(c, err)
		return
	}
	Success(c, rsp)
}

響應(yīng)格式統(tǒng)一為:

// 響應(yīng)
type Rsp struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
	Data any    `json:"data,omitempty"`
}

Success()用于處理成功情況:

// 請(qǐng)求成功
func Success(c *gin.Context, data any) {
	ginRsp(c, http.StatusOK, &Rsp{
		Code: ErrCodeOK,
		Msg:  "ok",
		Data: data,
	})
}

其余同理。

如果無(wú)法使用Do()和DoOpt()則可以使用這些方法。

處理錯(cuò)誤

一般我們都需要在出錯(cuò)時(shí)帶上一個(gè)業(yè)務(wù)錯(cuò)誤碼,方便客戶端處理。因此我們需要提供一個(gè)合適的error類型:

// 錯(cuò)誤
type Error struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
}

我們提供了一些函數(shù)方便使用Error,對(duì)應(yīng)NewError()、ToError()、ErrCode()、ErrMsg()、ErrEqual()函數(shù)。

比如NewError()生成一個(gè)Error類型error:

// 通過(guò)code和msg產(chǎn)生一個(gè)錯(cuò)誤
func NewError(code int, msg string) error {
	return &Error{
		Code: code,
		Msg:  msg,
	}
}

請(qǐng)求上下文操作

Gin的請(qǐng)求是鏈?zhǔn)教幚淼?,也就是多個(gè)handler順序的處理一個(gè)請(qǐng)求,比如:

        reqFunc := func(c *gin.Context, req *UpdateUserInfoReq) {
		req.UID = ginrest.GetKey[int](c, KeyUserID)
	}
        // 認(rèn)證,綁定UID,處理
	e.POST("/user/info/update", Verify, ginrest.Do(reqFunc, UpdateUserInfo))

這個(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里面(下面介紹)。

// 認(rèn)證
func Verify(c *gin.Context) {
	// 認(rèn)證處理
	// ...
	// 忽略認(rèn)證的具體邏輯
	ginrest.SetKey(c, KeyUserID, uid)
}

請(qǐng)求結(jié)構(gòu)體處理

上面我們?cè)O(shè)置了請(qǐng)求上下文,比如UID,但是其實(shí)我們并不知道具體這個(gè)UID是需要設(shè)置到req里的哪個(gè)字段,因此我們提供了一個(gè)回調(diào)函數(shù)ReqFunc(),用于設(shè)置Req:

	// 這里↓
        reqFunc := func(c *gin.Context, req *UpdateUserInfoReq) {
		req.UID = ginrest.GetKey[int](c, KeyUserID)
	}
        // 認(rèn)證,綁定UID,處理
	e.POST("/user/info/update", Verify, ginrest.Do(reqFunc, UpdateUserInfo))

如果這個(gè)庫(kù)的設(shè)計(jì)不符合具體的業(yè)務(wù),也可以按照這種思路去封裝一個(gè)類似的庫(kù),只要盡可能的統(tǒng)一請(qǐng)求、響應(yīng)的格式,就可以減少很多重復(fù)的模板代碼。

以上就是Go Ginrest實(shí)現(xiàn)一個(gè)RESTful接口的詳細(xì)內(nèi)容,更多關(guān)于Go Ginrest實(shí)現(xiàn)RESTful接口的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang原生實(shí)現(xiàn)JWT的示例代碼

    golang原生實(shí)現(xiàn)JWT的示例代碼

    在Go中實(shí)現(xiàn)JWT驗(yàn)證,可以通過(guò)標(biāo)準(zhǔn)庫(kù)crypto/hmac、crypto/sha256和encoding/base64來(lái)編寫自己的JWT,本文就詳細(xì)的來(lái)介紹一下,感興趣的可以了解下
    2023-05-05
  • gorm update傳入struct對(duì)象,零值字段不更新的解決方案

    gorm update傳入struct對(duì)象,零值字段不更新的解決方案

    這篇文章主要介紹了gorm update傳入struct對(duì)象,零值字段不更新的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • 如何有效控制Go線程數(shù)實(shí)例探究

    如何有效控制Go線程數(shù)實(shí)例探究

    這篇文章主要為大家介紹了如何有效控制?Go?線程數(shù)的問(wèn)題探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • golang中一種不常見(jiàn)的switch語(yǔ)句寫法示例詳解

    golang中一種不常見(jiàn)的switch語(yǔ)句寫法示例詳解

    這篇文章主要介紹了golang中一種不常見(jiàn)的switch語(yǔ)句寫法,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-05-05
  • golang雙指針快速排序的實(shí)現(xiàn)代碼

    golang雙指針快速排序的實(shí)現(xiàn)代碼

    這篇文章主要介紹了golang雙指針快速排序的實(shí)現(xiàn)代碼,通過(guò)實(shí)例代碼補(bǔ)充介紹了Golang實(shí)現(xiàn)快速排序和歸并排序以及堆排序算法全注釋,需要的朋友可以參考下
    2024-03-03
  • Go語(yǔ)言導(dǎo)出內(nèi)容到Excel的方法

    Go語(yǔ)言導(dǎo)出內(nèi)容到Excel的方法

    這篇文章主要介紹了Go語(yǔ)言導(dǎo)出內(nèi)容到Excel的方法,涉及Go語(yǔ)言操作excel的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • golang字符串拼接實(shí)現(xiàn)方式和區(qū)別對(duì)比

    golang字符串拼接實(shí)現(xiàn)方式和區(qū)別對(duì)比

    本文介紹了Go語(yǔ)言中字符串拼接的多種方法及其優(yōu)缺點(diǎn),推薦使用strings.Builder進(jìn)行頻繁拼接以優(yōu)化內(nèi)存分配和性能,同時(shí),還討論了通過(guò)sync.Pool優(yōu)化高頻創(chuàng)建的對(duì)象,以減少垃圾回收壓力,感興趣的朋友一起看看吧
    2025-02-02
  • golang中對(duì)

    golang中對(duì)"引用傳遞"的誤解

    這篇文章主要介紹了golang中對(duì)“引用傳遞”的誤解,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-06-06
  • go?singleflight緩存雪崩源碼分析與應(yīng)用

    go?singleflight緩存雪崩源碼分析與應(yīng)用

    這篇文章主要為大家介紹了go?singleflight緩存雪崩源碼分析與應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • golang利用redis和gin實(shí)現(xiàn)保存登錄狀態(tài)校驗(yàn)登錄功能

    golang利用redis和gin實(shí)現(xiàn)保存登錄狀態(tài)校驗(yàn)登錄功能

    這篇文章主要介紹了golang利用redis和gin實(shí)現(xiàn)保存登錄狀態(tài)校驗(yàn)登錄功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-01-01

最新評(píng)論

鞍山市| 博乐市| 许昌县| 米易县| 鄄城县| 辛集市| 文登市| 漯河市| 阳东县| 盈江县| 芜湖县| 正阳县| 瓮安县| 美姑县| 漯河市| 曲靖市| 宝应县| 新绛县| 凯里市| 阳山县| 伊春市| 广西| 库车县| 靖远县| 博野县| 包头市| 迭部县| 金溪县| 乌拉特前旗| 都匀市| 通河县| 二连浩特市| 阜新市| 巫溪县| 灵寿县| 花垣县| 临泽县| 黄浦区| 色达县| 河间市| 连平县|