gin context和官方context_Go Web 小技巧(一)简化Gin接口代码

不知道大家在使用 Gin 构建 API 服务时有没有这样的问题:

  1. 参数绑定的环节可不可以自动处理?
  2. 错误可不可以直接返回,不想写空 return, 漏写就是 bug

本文通过简单地封装,利用 go 的接口特性,提供一个解决上述两个问题的思路

一、解决过程

1.1 刚开始时写 API 服务时

我们刚开始使用 Gin 写 API 服务时,一般会按照官方文档上的 这么写

// User 用户结构
type User struct {UserName string
}// CreateUser 创建用户
func CreateUser(ctx *gin.Context) {var params Userif err := ctx.ShouldBind(&params); err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"code": 400,"msg":  "参数错误",})logrus.Errorf("params err, %v", params)return}// 一些其他的业务逻辑 ...ctx.JSON(http.StatusOK, gin.H{"code": 0,"msg":  "创建成功",})
}func main() {r := gin.Default()r.POST("user", CreateUser)if err := r.Run(":8080"); err != nil {logrus.Fatalf("can not start serve: %v", err)}
}

1.2 封装返回值

我们写了一段时间之后,会发现,我们的返回值的结构是固定的,为什么不抽象一下呢,所以我们创建了一个结构体 Resp ,并且封装了两个方法用于成功和失败这两种状态的返回

// resp.go// Resp 返回
type Resp struct {Code intMsg  stringData interface{}
}// ErrorResp 错误返回值
func ErrorResp(ctx *gin.Context, code int, msg string, data ...interface{}) {resp(ctx, code, msg, data...)
}// SuccessResp 正确返回值
func SuccessResp(ctx *gin.Context, msg string, data ...interface{}) {resp(ctx, 0, msg, data...)
}// resp 返回
func resp(ctx *gin.Context, code int, msg string, data ...interface{}) {resp := Resp{Code: code,Msg:  msg,Data: data,}if len(data) == 1 {resp.Data = data[0]}ctx.JSON(http.StatusOK, resp)
}

添加这个方法之后,我们再看一下 CreateUser 这个方法,成功的从 16 行变到了 12 行

// main.go
// CreateUser 创建用户
func CreateUser(ctx *gin.Context) {var params Userif err := ctx.ShouldBind(&params); err != nil {ErrorResp(ctx, 400, "参数错误")logrus.Errorf("params err, %v", params)return}// 一些其他的业务逻辑 ...SuccessResp(ctx, "创建成功")
}

1.3 两个痛点

上面的方法还不够完整,我们还是有许多重复的逻辑,可以发现我们在写的绝大多数 API 大概都是这样:

  1. 参数绑定 & 校验
  2. 业务逻辑
  3. 返回

这里面有两个痛点:

  1. 参数绑定的环节可不可以自动处理?
  2. 错误可不可以直接返回,不想写空 return, 漏写就是 bug
   // 不想写大量这种重复的代码var params Userif err := ctx.ShouldBind(&params); err != nil {// 下面这三行是不是可以合并成一行ErrorResp(ctx, 400, "参数错误")logrus.Errorf("params err, %v", params)return}

1.4 使用接口封装请求

上面的这两个痛点我们可以通过一个辅助函数解决

// Requester 请求
type Requester interface {Request(ctx *gin.Context) (*Resp, error)
}// Handle 请求
func Handle(r Requester) gin.HandlerFunc {return func(ctx *gin.Context) {resp, err := request(r, ctx)if err != nil {var code *errcode.Errorif !errors.As(err, &code) {code = errcode.Unknown.Wrap(err)}resp = &Resp{Code: code.Code,Msg:  code.String(),}_ = ctx.Error(err)}ctx.JSON(http.StatusOK, resp)}
}func request(r Requester, ctx *gin.Context) (*controller.Resp, error) {// 参数绑定if err := ctx.ShouldBind(r); err != nil {return nil, errcode.ErrParams.Wrap(err)}return r.Request(ctx)
}

这样我们只需要实现这个 Requester, 写 API 时只需要关注业务逻辑就可以了

// CreateUser 创建用户
type CreateUser struct {UserName string
}func (u *User) Request(ctx *gin.Context) (*Resp, error) {// 业务逻辑// 返回成功值
}func main() {r := gin.Default()r.POST("user", Handle(&CreateUser))if err := r.Run(":8080"); err != nil {logrus.Fatalf("can not start serve: %v", err)}
}

上面的代码有一个 bug 不知道大家发现没有,我们上一次请求的参数会被带到下一次请求当中

// Handle 请求
func Handle(r Requester) gin.HandlerFunc {return func(ctx *gin.Context) {// 创建一个新的 Requester, 避免将上一次的参数带到下一次当中if reflect.TypeOf(r).Kind() != reflect.Ptr {panic("must be a pointer")}req := reflect.New(reflect.ValueOf(r).Elem().Type()).Interface().(Requester)resp, err := request(req, ctx)if err != nil {var code *errcode.Errorif !errors.As(err, &code) {code = errcode.Unknown.Wrap(err)}resp = &Resp{Code: code.Code,Msg:  code.String(),}_ = ctx.Error(err)}ctx.JSON(http.StatusOK, resp)}
}

二、总结

大概这样差不多就 ok 了,还有很多可以完善的点,这里有一些思路,有的已经做了,有的还在路上

  1. 每次注册都写 Handle(&CreateUser) 还是有点麻烦?
可以封装一下 gin.IRouter 这个接口,这样注册接口就可以和原来一样了

2. 参数绑定如果我需要多次绑定怎么办?

可以添加一个接口,如果实现了这个接口就执行以下,对于有特殊的参数校验之类的也可以采用类似的方式处理
   type Binder interface {Bind(ctx *gin.Context) error}func request(r Requester, ctx *gin.Context) (*controller.Resp, error) {// 参数绑定if err := ctx.ShouldBind(r); err != nil {return nil, errcode.ErrParams.Wrap(err)}// 其余参数绑定if b, ok := r.(Binder); ok {if err := b.Bind(api); err != nil {return nil, errcode.ErrParams.Wrap(err)}}return r.Request(ctx)}

3. 怎么输出 API 文档?

可以和 swagger 之类的 API 文档结合, 利用 go generate 自动生成,顺便可以连接口注册都不用了,添加一行注释,自动注册接口,并且输出接口文档

 // @Router put /api/v1/userfunc(u *User) Request(ctx *gin.Context) (*Resp, error)

4. 能不能减少 CURD 代码?

可以实现,只需要采用约定的项目接口,可以 利用 go generate 直接自动生成简单的 CURD 代码

博客原文

Go Web 小技巧(一)简化Gin接口代码​lailin.xyz
d0547c9ca5f4d738fc029874d8a091a6.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/385990.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

7年老Android一次操蛋的面试经历,深度好文

Java基础 Java Object类方法HashMap原理,Hash冲突,并发集合,线程安全集合及实现原理HashMap 和 HashTable 区别HashCode 作用,如何重载hashCode方法ArrayList与LinkList区别与联系GC机制Java反射机制,Java代理模式Jav…

Hadoop大数据应用生态圈中最主要的组件及其关系

Hadoop Common Hadoop Common是在Hadoop0.2版本之后分离出来的HDFS和MapReduce独立子项目的内容,是Hadoop的核心部分,能为其他模块提供一些常用工具集,如序列化机制、Hadoop抽象文件系统FileSystem、系统配置工具Configuration,并…

7年老Android一次操蛋的面试经历,系列教学

公司的需求 不同的公司,不同的需求现在的市场上,公司很多,大致上可以归纳为两个大类:大公司和小公司,他们招聘时对人才的需求也不一样。 小公司 小公司他们一般急需的是能够投入工作的人才,因为公司规模…

丁香园 武汉 神童_杭州、武汉、成都哪个城市更适合程序员发展

很多朋友讨论起房价和职业发展机会,都会提到这三个城市,有的人认为目前杭州房价太贵了,生活成本高,华中的武汉和西部崛起的成都都在鼓励高新技术发展并且有了一定成果,在选择职业发展和定居城市之间该如何取舍呢&#…

Windows 7 64位系统上搭建Hadoop伪分布式环境(很详细)

在开始配置前,我们先了解Hadoop的三种运行模式。 Hadoop的三种运行模式 独立(或本地)模式:无需运行任何守护进程,所有程序都在同一个JVM上执行。在独立模式下测试和调试MapReduce程序很方便,因此该模式在…

7年老Android一次操蛋的面试经历,讲的太透彻了

由于涉及到的面试题较多导致篇幅较长,我根据这些面试题所涉及到的常问范围总结了并做出了一份学习进阶路线图​​​​​​​及面试题答案免费分享给大家,文末有免费领取方式! View面试专题 View的滑动方式View的事件分发机制View的加载流程…

80后程序员月薪30K+感慨中年危机,面试必问!

说说程序猿行业 现在社会上给IT行业贴上了几个标签:高薪、高危、高大上、秃顶(哈哈)。这些标签我相比大家都比较清楚,至于为什么是这些标签呢?而且这些标签是真实还是假象呢? 高薪 作为IT行业来说&#…

华为照片在哪个文件夹_原来华为手机还能这样清理垃圾,怪不得你的手机可以多用5年...

对于目前市场上的智能手机来说,大家的手机功能都是差不多的,除了一些外观上的差别之外,最大的区别就是手机的内存,但是很多朋友却表示手机内存很大,但是没用多久,手机就会出现卡顿或者是运行速度变慢的现象…

996页阿里Android面试真题解析火爆全网,全网首发!

在安卓系统中: 当系统内存不足时,Android系统将根据进程的优先级选择杀死一 些不太重要的进程,优先级低的先杀死。进程优先级从高到低如下。 前台进程 处于正在与用户交互的activity与前台activity绑定的service调用了startForeground&…

996页阿里Android面试真题解析火爆全网,分享面经!

导语 学历永远是横在我们进人大厂的一道门槛,好像无论怎么努力,总能被那些985,211 按在地上摩擦! 不仅要被“他们”看不起,在HR挑选简历,学历这块就直接被刷下去了,连证明自己的机会也没有,学…

access ole 对象 最大长度_Redis 数据结构和对象系统,有这 12 张图就够了!

作者 | 程序员历小冰责编 | 林瑟Redis 是一个开源的 key-value 存储系统,它使用六种底层数据结构构建了包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象的对象系统。 今天我们就通过 12 张图来全面了解一下它的数据结构和对象系统的实现原理。01数据结…

【面试总结】2021Java春招面试经历

三、堆空间 基本描述 JVM启动时创建堆区,是内存管理的核心区,通常情况下也是最大的内存空间,是被所有线程共享的,几乎所有的对象实例都要在堆中分配内存,所以这里也是垃圾回收的重点空间。 堆栈关系 栈是JVM运行时的…

【高级Java架构师系统学习】最新Java高级面试题汇

性能调优 影响MySQLServer 性能的相关因素 商业需求对性能的影响系统架构及实现对性能的影响Query语句对系统性能的影响Schema设计对系统的性能影响硬件环境对系统性能的影响 MySQL 数据库锁定机制 MySQL锁定机制简介各种锁定机制分析合理利用锁机制优化MySQL MySQL数据库Qu…

vue 安装指定版本swiper_Vue中的runtime-only和runtime-compiler

在我们使用vue-cli的时候,会提示你安装的版本可以看到有两种版本:Routime Only和Runtime Compiler版本1.Runtime Only - 代码中不可以有任何template 性能更高在该版本下,通常需要借助如webpack的vue-loader发工具把.vue文件编译成js因为是在…

一文搞懂JVM架构:入职3个月的Java程序员面临转正

Java基础 1.JAVA 中的几种数据类型是什么,各自占用多少字节。 2.String 类能被继承吗,为什么。 3. 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗? 4. String 属于基础的数据类型吗? 5.…

不显示调用super_让不懂编程的人爱上iPhone开发(2017秋iOS11+Swift4+Xcode9版)-第11篇

欢迎回到我们的iPhone开发教程系列,让我们继续前进吧。重新来过别害怕,哥不是让你抛弃之前所有的源代码,从零开始重新构建这个项目!这里说的是游戏界面里面的“Start over”按钮。在我们的to-do清单里面曾经提到过,这个…

一文搞懂JVM架构:跳槽面试大厂被拒

正文 在实际的工作项目中, 缓存成为高并发、高性能架构的关键组件 ,那么Redis为什么可以作为缓存使用呢?首先可以作为缓存的两个主要特征: 在分层系统中处于内存/CPU具有访问性能良好,缓存数据饱和,有良好…

全局变量_Python函数中的全局变量与局部变量

# a,b变量是全局变量,在整个py文件中都可以访问a 11b 12# 定义一个函数def first():# 这个变量是函数内部定义的变量,属于局部变量,只能在函数中使用c "Hello"# 大括号{} 是format()函数的用法,格式化print("c {}".format(c))# 如果局部变量定义的名称…

一文详解:字节面试官必问的Mysql锁机制

一面 1 自我介绍和项目 2 Java的内存分区 3 Java对象的回收方式,回收算法。 4 CMS和G1了解么,CMS解决什么问题,说一下回收的过程。 5 CMS回收停顿了几次,为什么要停顿两次。 6 Java栈什么时候会发生内存溢出,Jav…

install npm 到某个文件下执行_你可能不知道的 npm 依赖管理那些事

点击上方蓝字关注我们npm 是 Node.js 默认的、以 JavaScript 编写的包管理工具,如今,它已经成为世界上最大的包管理工具,是每个前端开发者必备的工具。不知你是否遇到过下面问题:哎?我本地明明是好的,线上的…