包装RESTful形式的接口步骤

网友投稿 322 2022-11-11

包装RESTful形式的接口步骤

背景

基于现在微服务或者服务化的思想,我们大部分的业务逻辑处理函数都是长这样的:

比如grpc服务端:

grpc客户端:

有些服务我们需要把它包装为RESTful形式的接口,一般需要经历以下步骤:

指定HTTP方法、URL

鉴权

参数绑定

处理请求

处理响应

可以发现,参数绑定、处理响应几乎都是一样模板代码,鉴权也基本上是模板代码(当然有些鉴权可能比较复杂)。

而Ginrest库就是为了消除这些模板代码,它不是一个复杂的框架,只是一个简单的库,辅助处理这些重复的事情,为了实现这个能力使用了Go1.18的泛型。

特性

这个库提供以下特性:

封装RESTful请求响应

封装RESTful请求为标准格式服务

默认使用统一数字错误码格式:[0, 4XXXX, 5XXXX]

默认使用标准错误格式:Error{code, msg}

默认统一状态码[200, 400, 500]

提供Recovery中间件,统一panic时的响应格式

提供SetKey()、GetKey()方法,用于存储请求上下文(泛型)

提供ReqFunc(),用于设置Req(泛型)

使用例子

首先我们实现两个简单的服务:

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()里面每个接口都只需要一行代码!

运行上面代码,然后尝试访问接口,可以看到返回结果:

实现原理

Do()和DoOpt()都会转发到do(),它其实是一个模板函数,把脏活累活给处理了:

功能列表

处理请求

用于把一个标准服务封装为一个RESTfulgin.HandlerFunc,对应Do()、DoOpt()函数。

DoOpt()相比于Do()多了一个opts参数,因为很多rpc框架客户端都有一个opts参数作为结尾。

还有一个BindJSON(),用于把请求体包装为一个Req结构体:

如果无法使用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()用于处理成功情况:

其余同理。

如果无法使用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))

如果这个库的设计不符合具体的业务,也可以按照这种思路去封装一个类似的库,只要尽可能的统一请求、响应的格式,就可以减少很多重复的模板代码。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Linux安装PHP
下一篇:keepalived+nginx+tomcat
相关文章

 发表评论

暂时没有评论,来抢沙发吧~