Go Ginrest实现一个RESTful接口
来源:脚本之家
时间:2023-02-24 20:55:55 462浏览 收藏
本篇文章向大家介绍《Go Ginrest实现一个RESTful接口》,主要包括接口、RESTful、Ginrest,具有一定的参考价值,需要的朋友可以参考一下。
背景
基于现在微服务或者服务化的思想,我们大部分的业务逻辑处理函数都是长这样的:
比如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形式的接口,一般需要经历以下步骤:
- 指定HTTP方法、URL
- 鉴权
- 参数绑定
- 处理请求
- 处理响应
可以发现,参数绑定、处理响应几乎都是一样模板代码,鉴权也基本上是模板代码(当然有些鉴权可能比较复杂)。
而Ginrest库就是为了消除这些模板代码,它不是一个复杂的框架,只是一个简单的库,辅助处理这些重复的事情,为了实现这个能力使用了Go1.18的泛型。
特性
这个库提供以下特性:
- 封装RESTful请求响应
- 封装RESTful请求为标准格式服务
- 封装标准格式服务处理结果为标准RESTful响应格式:Rsp{code, msg, data}
- 默认使用统一数字错误码格式:[0, 4XXXX, 5XXXX]
- 默认使用标准错误格式:Error{code, msg}
- 默认统一状态码[200, 400, 500]
- 提供Recovery中间件,统一panic时的响应格式
- 提供SetKey()、GetKey()方法,用于存储请求上下文(泛型)
- 提供ReqFunc(),用于设置Req(泛型)
使用例子
示例代码在: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接口》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于golang的知识点!
-
194 收藏
-
250 收藏
-
394 收藏
-
256 收藏
-
493 收藏
-
193 收藏
-
271 收藏
-
414 收藏
-
179 收藏
-
194 收藏
-
441 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习