0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

包装RESTful形式的接口步骤

马哥Linux运维 来源:马哥Linux运维 作者:马哥Linux运维 2022-09-22 09:52 次阅读

背景

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

比如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的泛型。

仓库地址:https://github.com/jiaxwu/ginrest

特性

这个库提供以下特性:

封装RESTful请求响应

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

封装标准格式服务处理结果为标准RESTful响应格式:Rsp{code, msg, data}

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

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

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

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

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

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

使用例子

示例代码在:https://github.com/jiaxwu/ginrest/blob/main/examples/main.go

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

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)}

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

请求1GET http://127.0.0.1:8000/user/info/get{    "uid": 10}响应1{    "code": 0,    "msg": "ok",    "data": {        "uid": 10,        "username": "user_10",        "age": 10    }}
请求2GET http://127.0.0.1:8000/user/info/get{    "uid": 1}响应2{    "code": 40100,    "msg": "user not exists"}
请求3POST 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))

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

审核编辑:彭静
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接口
    +关注

    关注

    33

    文章

    8612

    浏览量

    151282
  • 函数
    +关注

    关注

    3

    文章

    4332

    浏览量

    62681
  • 代码
    +关注

    关注

    30

    文章

    4791

    浏览量

    68673

原文标题:一行代码实现一个 RESTful 接口

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    请问LabVIEW通过无线WiFi采集数据 基于restful

    本帖最后由 eehome 于 2013-1-5 09:47 编辑 求助,有没有兄弟做过相关的项目啊,labview编写一个程序作为一个web service,基于restful。 需要将采集到
    发表于 03-22 18:08

    编写restful

    求助,有没有兄弟做过相关的项目啊,现在写了一个程序作为一个web service,基于restful。 需要将采集到的数据经过wifi传输,数据传输想用到restful。有没有推荐的自带的restful的wifi模块,或者有没有
    发表于 03-22 18:12

    ISTA 国际包装运输组织---电子产品包装运输考察

    ISTA 1A 测试标准中的步骤及内容1A测试步骤 固定放置振动测试 1、将包装件3面朝下放在振动台上 2、开启振动系统,使仪器以最低频率振动且振幅为1英寸 3、保持此振幅,同时逐渐增大振动频率
    发表于 08-04 17:37

    restful api设计规范

    清晰、符合标准、易于理解以及扩展方便等特点,受到越来越多网站的采用!Restful API接口规范包括以下部分:一、协议API与用户的通信协议,总是使用HTTPs协议。二、域名应该尽量将API部署在
    发表于 03-26 16:26

    python restful api学习技巧精选2

    python restful api 学习笔记2 快速开始
    发表于 09-16 13:39

    一文知道后端接口开发json,jsonp,restful

    json、jsonp/** * 后台接口开发 * json接口 * jsonp接口(解决跨域问题) * restful接口 */const
    发表于 11-04 07:22

    什么是restful以及restfulAPI的设计风格?

    如何理解restful架构?什么是restful API ? restful API的设计风格和序列化?restful API之请求与响应 ?
    发表于 11-04 08:25

    为什么需要接口接口电路有哪些形式

    什么是接口?为什么需要接口接口硬件包含哪些部分?接口软件有什么功能?接口电路有哪些形式?什么是
    发表于 12-23 07:27

    在4.3.1版的restful_server示例中找不到npm是怎么回事?

    从 Eclipse 输出:CMake Error at main/CMakeLists.txt:10 (message):C:/esp-431/ws431/restful_server/main
    发表于 02-17 07:31

    车载逆变电源包装

    车载逆变电源包装              包装主要是指车载应急电源在运输、销售过程中的包装
    发表于 01-04 13:42 580次阅读

    Constrained RESTful Environments (CoRE) Link Format

    Constrained RESTful Environments (CoRE) link Format,受限的RESTful环境链路格式
    发表于 11-26 15:23 6次下载

    Restful 和 RPC 是什么关系与区别

    本文详细介绍了关于Restful 和 RPC的关系与区别,详细分析请看下文。
    的头像 发表于 02-07 15:35 3.8w次阅读
    <b class='flag-5'>Restful</b> 和 RPC 是什么关系与区别

    弹簧包装机换包装薄膜步骤的简单介绍

    包装机来为她们提升工作效能。 弹簧包装机在颗粒物包裝领域公司的应用全过程中,许多都不清楚弹簧包装机换包装薄膜的步骤。今日我为大伙儿解读一下弹
    发表于 02-19 16:28 2026次阅读

    构建RESTful Web服务的过程

    本指南将引导您完成使用 Spring 创建“Hello, World”RESTful Web 服务的过程。
    的头像 发表于 09-06 15:47 710次阅读

    使用RESTful Web服务的过程

    本指南将引导您完成创建使用#spring# #spring认证# RESTful Web 服务的应用程序的过程。
    的头像 发表于 09-06 15:47 712次阅读