基于go-zero的api服务刨析并对比与gin的区别

zero路由与gin的区别

官网go-zero

go-zero是一个集成了各种工程实践的微服务框架,集多种功能于一体,如服务主要的API服务,RPC服务等。除了构建微服务工程外,zero也是一款性能优良的web框架,也可以构建单体web应用。

更多移步www.w3cschool.cn/go-zero。

go的web框架是很多的,例如github较为流行的有:

  1. Gin Go语言编写的HTTP Web框架,它以更好的性能实现了类似Martini的API,性能更好。
  2. Beego 面向Go编程语言的开源高性能web框架。
  3. Iris最快的Go语言Web框架,完备MVC支持
  4. Echo 高性能、极简Go语言Web框架。

这些的框架的路由方式都极具相似,一般步骤都为通过工具库提供的方法构建web引擎,配置路由,启动web服务器,配置监听端口等。如下:

//gin框架
r := gin.Default()
_ = r.Run()
// 或者启动原生服务
manners.ListenAndServe(":8888", r)
//gin框架配置路由
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {c.String(http.StatusOK, "pong")
})//路由组和其他路由方式
//通过路由将处理函数配置

gin框架中路由的处理函数是不限个数,从而实现了中间件的功能,另外gin.Context是路由的上下文连接,在不同路由路径下会自动解析该路径下的请求参数。

//iris服务app := iris.New()
app.Run(iris.Addr(":8080"))// 或者自定义链接方式与端口号
l, err := listenerCfg.NewListener("tcp", ":8080")
if err != nil {app.Logger().Fatal(err)
}
app.Run(iris.Listener(l))// 或者启动原生服务
app.Run(iris.Raw(&http.Server{Addr:":8080"}).ListenAndServe)
//iris路由app.Get("/", func(ctx iris.Context) {ctx.HTML("<h1> Hello from /contact </h1>")
})

这些框架路由原理很相似通过上下文连web容器,每个控制器独立解析该路由路径下的参数。

在实例项目中,路由需要分离出来,处理逻辑也是,对于这样原理的web框架,同一系列的路由路径下由一个接口实现或者类成员方法实现(所有方法分散不利于代码维护)。那么该实现方式就类似Java的实现方式。

在这里插入图片描述

如图所示/a1,/a2…都是/a系的,对应A类,分发到对应的路由就通过A类调用对应的方法即可。

//mainimport ("github.com/gin-gonic/gin""backend/controller"
)
func main() {//创建一个服务器引擎engine := gin.Default()//控制器传参c := new(controller.IndexController)c.RegisterRoute(engine)//配置服务器端口engine.Run("127.0.0.1:8080")}
// controller
type IndexController struct{}func (self IndexController) RegisterRoute(g *gin.Engine) {g.GET("/user", self.getUer)g.GET("/banner")
}func (IndexController) getUer(c *gin.Context) {lo := logic.IndexLogic{}notice := lo.GetUser()fmt.Println(notice)
}

gin路由分发案例

不同gin等路由分发的方式go-zero自成一派,具体请看go-zero的路由机制解析。zero是使用路由注册的方式,如下图所示:

在这里插入图片描述
编写该路由下的逻辑处理函数通过通过服务器引擎提供的方法将处理逻辑注册到对应路由中。这种方法的耦合度更低,显然这也更贴合go语言的特性。

//zero web服务import ("fmt""demo/apiservice/internal/config""demo/apiservice/internal/handler""demo/apiservice/internal/svc""github.com/zeromicro/go-zero/rest"
)func main() {var c config.Configc.Host = "0.0.0.0"c.Port = 8000server := rest.MustNewServer(c.RestConf)defer server.Stop()// headctx := svc.NewServiceContext(c)handler.RegisterHandlers(server, ctx)// bootfmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)server.Start()
}

在上述的代码中head到boot部分是服务器配置的不是实现web服务器实例的主要方法,该部分主要是配置第三方框架时需要,如日志文件等。核心代码也就,如下

//c.RestConf服务器配置(略)server := rest.MustNewServer(c.RestConf)
server.Start()

路由如下:

import ("net/http""demo/apiservice/internal/svc""github.com/zeromicro/go-zero/rest"
)func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {server.AddRoutes([]rest.Route{{Method:  http.MethodGet,Path:    "/from/:name",Handler: ApiserviceHandler(serverCtx),},//自定义路由{Method: http.MethodPost,Path: "/create",Handler: OrderCreateController(),},},)
}

通过注册的方式直接将路由与函数绑定,完全不需要结构体方法的继承关系,直接以函数作为第一操作单位。

路由处理函数

zero的路由处理函数满足的条件是返回类型为http.HandlerFunc的函数。细心的小伙伴可以就发现了这不是gin的路由处理函数的返回类型吗?实际上zero中HandlerFunc是基于Go原生的http库,而gin框架中该类型是对http.HandlerFunc的进一步封装。

在这里插入图片描述
http.HandlerFunc是一个参数为ResponseWriter,*Request的函数,学过Java的是不是秒懂了,在Java的Servlet中也存在HttpRquestHttpReponse,且该函数是请求报文与响应报文的封装对象,通过该对象可以直接获取请求参数和设置响应参数。

在zero中也是同样的原理,对于这样个参数如何获取获取请求参数和设置响应参数就不做过多介绍,有兴趣的看看源码。zero封装了httpx对这两个对象实现了解析,通过httpx库会更加方法的使用这两个参数。

只要满足返回类型为http.HandlerFunc就是zero的逻辑处理函数。

zero获取请求参数

前一节说到http.HandlerFunc类型的函数为zero的逻辑处理函数,而该类型又是参数必须是func(w http.ResponseWriter, r *http.Request)的函数。zero封装了httpx库提供了更加方便简洁方式使用这两个参数。

官方教程:github.com/zeromicro/go-zero/rest/httpx

httpx库内容不多,看源码很容器看懂,就是四种请求参数的解析。

在这里插入图片描述

package httpximport ("io""net/http""strings""sync/atomic""github.com/zeromicro/go-zero/core/mapping""github.com/zeromicro/go-zero/core/validation""github.com/zeromicro/go-zero/rest/internal/encoding""github.com/zeromicro/go-zero/rest/internal/header""github.com/zeromicro/go-zero/rest/pathvar"
)const (formKey           = "form"pathKey           = "path"maxMemory         = 32 << 20 // 32MBmaxBodyLen        = 8 << 20  // 8MBseparator         = ";"tokensInAttribute = 2
)var (formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues())pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues())validator       atomic.Value
)// Validator defines the interface for validating the request.
type Validator interface {// Validate validates the request and parsed data.Validate(r *http.Request, data any) error
}// Parse parses the request.
func Parse(r *http.Request, v any) error {if err := ParsePath(r, v); err != nil {return err}if err := ParseForm(r, v); err != nil {return err}if err := ParseHeaders(r, v); err != nil {return err}if err := ParseJsonBody(r, v); err != nil {return err}if valid, ok := v.(validation.Validator); ok {return valid.Validate()} else if val := validator.Load(); val != nil {return val.(Validator).Validate(r, v)}return nil
}// ParseHeaders parses the headers request.
func ParseHeaders(r *http.Request, v any) error {return encoding.ParseHeaders(r.Header, v)
}// ParseForm parses the form request.
func ParseForm(r *http.Request, v any) error {params, err := GetFormValues(r)if err != nil {return err}return formUnmarshaler.Unmarshal(params, v)
}// ParseHeader parses the request header and returns a map.
func ParseHeader(headerValue string) map[string]string {ret := make(map[string]string)fields := strings.Split(headerValue, separator)for _, field := range fields {field = strings.TrimSpace(field)if len(field) == 0 {continue}kv := strings.SplitN(field, "=", tokensInAttribute)if len(kv) != tokensInAttribute {continue}ret[kv[0]] = kv[1]}return ret
}// ParseJsonBody parses the post request which contains json in body.
func ParseJsonBody(r *http.Request, v any) error {if withJsonBody(r) {reader := io.LimitReader(r.Body, maxBodyLen)return mapping.UnmarshalJsonReader(reader, v)}return mapping.UnmarshalJsonMap(nil, v)
}// ParsePath parses the symbols reside in url path.
// Like http://localhost/bag/:name
func ParsePath(r *http.Request, v any) error {vars := pathvar.Vars(r)m := make(map[string]any, len(vars))for k, v := range vars {m[k] = v}return pathUnmarshaler.Unmarshal(m, v)
}// SetValidator sets the validator.
// The validator is used to validate the request, only called in Parse,
// not in ParseHeaders, ParseForm, ParseHeader, ParseJsonBody, ParsePath.
func SetValidator(val Validator) {validator.Store(val)
}func withJsonBody(r *http.Request) bool {return r.ContentLength > 0 && strings.Contains(r.Header.Get(header.ContentType), header.ApplicationJson)
}
  1. xxx?a=xxx?&b=xxx类型

该类型可以分为超链接和表单,对应的方法为httpx.ParseForm

  1. /xxx/:a/:b类型参数

对应方法为httpx.ParsePath

  1. 请求头参数

对应方法为http.ParseHeaders

4.请求体参数

对应方法为httpx.ParseJsonBody

httpx为zero库下的github.com/zeromicro/go-zero/rest/httpx

另外还有httpx.Parse方法,能够自动解析,对应参数,源码如下:

// Parse parses the request.
func Parse(r *http.Request, v any) error {if err := ParsePath(r, v); err != nil {return err}if err := ParseForm(r, v); err != nil {return err}if err := ParseHeaders(r, v); err != nil {return err}if err := ParseJsonBody(r, v); err != nil {return err}if valid, ok := v.(validation.Validator); ok {return valid.Validate()} else if val := validator.Load(); val != nil {return val.(Validator).Validate(r, v)}return nil
}

zero设置响应参数

http.HandlerFunc函数可知,路由处理函数是一个字符流,用于写入响应数据。响应数据就相对复杂了,应为响应数据一般要包含响应码,而响应吗数量非常多从100~600之间都有响应码,因此返回携带准确响应码的就十分棘手。

HTTP

一般情况下大多数框架响应码都是开发者定义的。响应分为五类:信息响应(100–199),成功响应(200–299),重定向(300–399),客户端错误(400–499)和服务器错误 (500–599)。

最常用的响应码有:

  • 200 - 请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 400 - 客户端请求的语法错误,服务器无法理解
  • 403 - 服务器理解请求客户端的请求,但是拒绝执行此请求
  • 404 - 请求的资源(网页等)不存在
  • 500 - 内部服务器错误
  • 502 - 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应

在这里插入图片描述

httpx库中只有三种种响应状态,成功、失败和自定义。前者是以Ok为前缀的方法,后者是以Error为前缀的方法。

// Error writes err into w.
func Error(w http.ResponseWriter, err error, fns ...func(w http.ResponseWriter, err error)) {lock.RLock()handler := errorHandlerlock.RUnlock()doHandleError(w, err, handler, WriteJson, fns...)
}// ErrorCtx writes err into w.
func ErrorCtx(ctx context.Context, w http.ResponseWriter, err error,fns ...func(w http.ResponseWriter, err error)) {lock.RLock()handlerCtx := errorHandlerCtxlock.RUnlock()var handler func(error) (int, any)if handlerCtx != nil {handler = func(err error) (int, any) {return handlerCtx(ctx, err)}}writeJson := func(w http.ResponseWriter, code int, v any) {WriteJsonCtx(ctx, w, code, v)}doHandleError(w, err, handler, writeJson, fns...)
}// Ok writes HTTP 200 OK into w.
func Ok(w http.ResponseWriter) {w.WriteHeader(http.StatusOK)
}// OkJson writes v into w with 200 OK.
func OkJson(w http.ResponseWriter, v any) {WriteJson(w, http.StatusOK, v)
}// OkJsonCtx writes v into w with 200 OK.
func OkJsonCtx(ctx context.Context, w http.ResponseWriter, v any) {WriteJsonCtx(ctx, w, http.StatusOK, v)
}

当响应数据需求不高可以直接使用这两个方法,当需要定制化响应参数时就需要使用自定义的返回响应报文了。

// WriteJson writes v as json string into w with code.
func WriteJson(w http.ResponseWriter, code int, v any) {if err := doWriteJson(w, code, v); err != nil {logx.Error(err)}
}// WriteJsonCtx writes v as json string into w with code.
func WriteJsonCtx(ctx context.Context, w http.ResponseWriter, code int, v any) {if err := doWriteJson(w, code, v); err != nil {logx.WithContext(ctx).Error(err)}
}

WriteJsonWriteJsonCtx可以定义返回状态码即code参数。前者不带配置项,后者携带配置项。v为响应体数据,为任意类型。由函数名可以看出返回类型为json字符串,由于http时字符流传输,json字符串是前后端传输的最轻量级的数据传输格式。

v是任意类型,也就是说,任何类型传参后都由框架自动序列化为json字符串。

// create
func OrderCreateController() http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {//获取请求参数var req models.Ordererr := httpx.ParseJsonBody(r, &req)if err != nil {//fmt.Printf("ordercontoller err:%v", err)httpx.WriteJson(w, 500, fmt.Sprintf("ordercontoller err:%v", err))return}err = orderlogic.OrderLogic.Create(req)if err != nil {//fmt.Printf("order create err:%v", err)httpx.WriteJson(w, 500, fmt.Sprintf("order create err:%v", err))return}httpx.OkJson(w, map[string]string{"code": "200", "message": "插入成功!"})}
}

在这里插入图片描述

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

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

相关文章

最新AI创作系统V5.0.2+支持GPT4+支持ai绘画+实时语音识别输入+文章资讯发布功能+用户会员套餐

最新AI创作系统V5.0.2支持GPT4支持ai绘画实时语音识别输入文章资讯发布功能用户会员套餐&#xff01; AI创作系统一、源码系统介绍二、AI创作系统程序下载三、安装教程四、主要功能展示五、更新日志 AI创作系统 1、提问&#xff1a;程序已经支持GPT3.5、GPT4.0接口 2、支持三种…

Maven 配置本地jar,通过下载第三方jar包,然后手动配置maven jar包依赖 例如:IKExpression

说明&#xff1a;有时候有一些jar包 maven中央仓库和阿里云仓库没有收录的jar包需要手动下载至本地进行手动添加maven依赖&#xff0c;就拿 IK表达式 IKExpression jar 包来说 第一步 下载IKExpression 包 没有这个包的同学可以点击下载阿里云盘分享 第二步 找到自己项目本地…

Redis 五种基本数据结构及基本使用

一、数据结构 二、使用 2.1 String 的使用 Redis String 一个键对应一个值&#xff0c;并且是二进制安全的&#xff0c;值可以是图片或者序列化后的对象。 一个键最大能存储 512 MB。 2.1.1 set 命令的使用 set key value127.0.0.1:6379> set name yunhu OK 127.0.0.1:…

二十三种设计模式第十四篇--策略模式

策略模式&#xff1a;主要围绕一个类的行为或者其算法在运行时更改&#xff0c;也是一种行为型模式。 在软件开发中&#xff0c;我们经常遇到需要根据不同的情况选择不同算法或行为的情况。传统的做法是使用大量的条件语句来实现这种逻辑&#xff0c;但这样的实现方式往往难以…

【花雕】全国青少年机器人技术一级考试备考实操搭建手册9

随着科技的不断进步&#xff0c;机器人技术已经成为了一个重要的领域。在这个领域中&#xff0c;机械结构是机器人设计中至关重要的一部分&#xff0c;它决定了机器人的形态、运动方式和工作效率。对于青少年机器人爱好者来说&#xff0c;了解机械结构的基础知识&#xff0c;掌…

[MySQL]MySQL表的约束

[MySQL]表的约束 文章目录 [MySQL]表的约束1. 约束的概念2. 空属性(null/not null)3. 默认值(default)4. 列描述(comment)5. 填充零(zerofill)6. 主键(primary key)7. 自增长(auto_increment)8. 唯一键(unique)9. 外键(foreign key) 1. 约束的概念 数据库通过技术手段限制数据的…

ADS笔记,新旧两组仿真数据进行绘图和列表对比

做个笔记&#xff0c;以防遗忘 ADS版本&#xff1a;2023 原理图器件参数的不同&#xff0c;怎么进行对比观看&#xff0c;操作如下 目录 一、数据绘图对比二、数据列表对比 一、数据绘图对比 选择Simulation Setting 然后修改原理图器件的参数&#xff0c;再次重复之前的操作…

MySQL自治平台建设的内核原理及实践(上)

本文整理自美团技术沙龙第75期的主题分享《美团数据库攻防演练建设实践》&#xff0c;系超大规模数据库集群保稳系列&#xff08;内含4个议题的PPT及视频&#xff09;的第4篇文章。 本文作者在演讲后根据同学们的反馈&#xff0c;补充了很多技术细节&#xff0c;跟演讲&#xf…

核磁共振常用的文件格式介绍:NIfTI volume 格式 (*.nii), GIFTI 格式 (*.gii), CIFTI 格式 (*.nii)

核磁共振常用的文件格式介绍:NIfTI volume 格式, GIFTI 格式, CIFTI 格式 NIfTI volume 格式 (*.nii)GIFTI 格式 (*.gii)CIFTI 格式 (*.nii)自定义工作台 (wb_view) 文件:Scene file文件 (*.scene)规格文件 (*.spec)Spec file (*.border)Foci (*.foci)Trajectory file (*.t…

【数据结构】栈和队列详解

⭐️ 往期相关文章 ✨链接1&#xff1a;数据结构和算法的概念以及时间复杂度空间复杂度详解 ✨链接2&#xff1a;【数据结构】手撕顺序表 ✨链接3&#xff1a;【数据结构】手撕单链表 ✨链接4&#xff1a;【数据结构】双向带头循环链表 ⭐️ 栈和队列 &#x1f320; 栈 栈是…

【计算机视觉 | 图像分割】arxiv 计算机视觉关于图像分割的学术速递(7 月 6 日论文合集)

文章目录 一、分割|语义相关(15篇)1.1 Prompting Diffusion Representations for Cross-Domain Semantic Segmentation1.2 ZJU ReLER Submission for EPIC-KITCHEN Challenge 2023: Semi-Supervised Video Object Segmentation1.3 Multi-Modal Prototypes for Open-Set Semanti…

Kafka学习笔记(基础篇)

目录 Kafka简介 消息队列 Kafka的应用场景 消息队列的两种模型 Kafka集群搭建 Kafka的生产者/消费者/工具 Kafka的基准测试工具 Kafka Java API开发 生产者程序开发 消费者程序开发 生产者使用异步方式生产消息 Kafka中的重要概念 消费者组 幂等性 事务编程 Ka…