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

news2024/11/16 21:30:13

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类调用对应的方法即可。

//main

import (
	"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.Config
	c.Host = "0.0.0.0"
	c.Port = 8000
	server := rest.MustNewServer(c.RestConf)
	defer server.Stop()
	// head
	ctx := svc.NewServiceContext(c)
	handler.RegisterHandlers(server, ctx)
	// boot
	fmt.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 httpx

import (
	"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 // 32MB
	maxBodyLen        = 8 << 20  // 8MB
	separator         = ";"
	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 := errorHandler
	lock.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 := errorHandlerCtx
	lock.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.Order
		err := 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.coloradmin.cn/o/730632.html

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

相关文章

并行计算框架Polars、Dask的数据处理性能对比

在Pandas 2.0发布以后&#xff0c;我们发布过一些评测的文章&#xff0c;这次我们看看&#xff0c;除了Pandas以外&#xff0c;常用的两个都是为了大数据处理的并行数据框架的对比测试。 本文我们使用两个类似的脚本来执行提取、转换和加载(ETL)过程。 测试内容 这两个脚本主…

【Linux】-Linux部署Javaweb项目

作者&#xff1a;学Java的冬瓜 博客主页&#xff1a;☀冬瓜的主页&#x1f319; 专栏&#xff1a;【Linux】 分享: 屋檐如悬崖 风铃如沧海 我等燕归来 时间被安排 演一场意外 你悄然走开 故事在城外 浓雾散不开 看不清对白 你听不出来 风声不存在 是我在感慨 梦想来是谁在窗台 …

【服务器】Python一行命令搭建HTTP服务器并外网访问 - 内网穿透

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 转载自cpolar极点云文章&#xff1a;【Python】快速简单搭建HTTP服务器并公网访问「cpolar内网穿透」…

【专题速递】音频生成、TTS和AIGC在音乐上的运用

// AIGC的发展为音频带来了什么&#xff1f;AIGC如何赋能音乐创作&#xff1f;如何识别虚假音频&#xff1f;TTS可以在哪种场景下解决特定问题&#xff1f;7月29日LiveVideoStackCon2023上海站音频新体验专场&#xff0c;为您解答。 音频新体验 随着多媒体和通信网络技术的不…

开利网络受邀参与广州三会企业数字化转型专题研讨会

​7月6日&#xff0c;开利网络受邀出席由广州三会于广州市黄埔区组织的“广州三会第六届理事会第八次会长联席会议”&#xff0c;并进行了主题为“企业数字化转型如何推动企业价值再造&#xff1f;”的专题分享会&#xff0c;为各位参会来宾分享企业数字化转型常见误区及数字化…

【IC设计】ICC1 workshop lab guide 学习笔记——Lab 2 Design Planning Task5-9

文章目录 ICC1 workshop lab guide2.5 Create P/G Rings Around Macro Groups2.6 Power Network Synthesis2.7 Check the Timing2.8 Write Out the DEF Floorplan File2.9 Create 2nd Pass Design Ready for Placement ICC1 workshop lab guide 2.5 Create P/G Rings Around M…

uniapp 发送全文件 支持App端ios、android,微信小程序,H5

由于uniapp提供的API在app端只能上传图片和视频&#xff0c;不能上传其他文件&#xff0c;说以只能借助插件了。 ios端用的这个插件 获取到文件对象 免费的 这个是返回一个 filePath 可用直接用于 uni.uploadFile 上传的路径&#xff0c;后面自己又改的File对象 全文件上传选择…

CAD绘制三维升旗台

首先绘制长方体的底座 用交叉对角线来定位&#xff0c;绘制一个小一点的矩形&#xff0c;用来定位 大概的效果&#xff1a; 沿着矩形的一个角绘制三个长方体&#xff0c;形成护栏 用阵列或者复制等形成四个角的护栏 旋转&#xff0c;换成真实的效果图&#xff1a; 添加一个圆…

文件共享平台Pingvin Share

本文完成于 2 月上旬。最近正好应网友要求折腾了 ClamAV&#xff0c;所以翻出来一起发了&#xff0c;可以作为 ClamAV 的一个应用示例&#xff1b; 什么是 Pingvin Share &#xff1f; Pingvin Share 是自托管文件共享平台&#xff0c;是 WeTransfer 的替代品。使用 Pingvin Sh…

【C语言基础】遍历

(꒪ꇴ꒪(꒪ꇴ꒪ ),我是祐言博客主页&#xff1a;C语言基础,Linux基础,软件配置领域博主&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff01;送给读者的一句鸡汤&#x1f914;&#xff1a;集中起来的意志可以击穿顽石!作者水平很有限&#xff0c;如果发现错误&…

S7-1200与ABB机器人进行SOCKET通信的具体方法示例

S7-1200与ABB机器人进行SOCKET通信的具体方法示例 SOCKET通信是一种基于TCP/IP协议的通信方式,提供了程序内部与外界通信的端口并为通信双方提供了数据传输通道。 ABB机器人实现SOCKET通信必须要在Communication选项中勾选616-1 PC Interface选项功能。 具体方法可参考以下内容…

SpringBoot+Vue酒店客房管理系统

&#x1f495;&#x1f495;作者&#xff1a;程序员徐师兄 个人简介&#xff1a;7 年大厂程序员经历&#xff0c;擅长Java、微信小程序、Python、Android等&#xff0c;大家有这一块的问题可以一起交流&#xff01; 各类成品java毕设 。javaweb&#xff0c;ssh&#xff0c;ssm&…

从YOLOv1到YOLOv8的YOLO系列最新综述【2023年4月】

作者&#xff1a;Juan R. Terven 、Diana M. Cordova-Esparaza 摘要&#xff1a;YOLO已经成为机器人、无人驾驶汽车和视频监控应用的核心实时物体检测系统。我们对YOLO的演变进行了全面的分析&#xff0c;研究了从最初的YOLO到YOLOv8每次迭代的创新和贡献。我们首先描述了标准…

Python 中的二维插值

本文展示了如何在 Python 中进行插值&#xff0c;并研究了不同的 2d 实现方法。 我们将讨论用于双变量插值的有用函数&#xff0c;例如 scipy.interpolate.interp2d、numpy.meshgrid 和 Python 中使用的用于平滑/插值 (RBF) 的径向基函数。 我们将使用 SciPy 和 Numpy 库实现插…

树莓派配置ubuntu server 22.04环境

背景 比起raspberry系统ubuntu更通用&#xff0c;结合公司项目开发需要&#xff0c;将树莓派4B刷上ubuntu server系统&#xff0c;并且安装LXDE桌面环境。 一波next 烧写镜像 用树莓派镜像烧录软件安装比较简单&#xff0c;选择操作系统&#xff1a;Other general-purpose O…

python psutil模块常用方法

psutil 是一个功能强大的跨平台第三方库&#xff0c;用于检索系统相关信息和进程管理。它提供了一些方便的函数和方法&#xff0c;可以获取 CPU 使用率、内存使用情况、磁盘信息、网络统计数据以及进程列表等。 1. 安装psutil pip install psutil2. 获取 CPU 使用率 import p…

flutter聊天界面-聊天列表 下拉加载更多历史消息

flutter聊天界面-聊天列表 下拉加载更多历史消息 在之前实现了flutter聊天界面的富文本展示内容、自定义表情键盘实现、加号【➕】更多展开相机、相册等操作Panel、消息气泡展示实现Flexible。这里把实现的聊天界面的滑动列表及下拉加载更多历史消息记录一下 聊天界面的列表使…

MySQL索引优化原则和失效情况

目录 1. 全值匹配2. 最佳左前缀法则3. 不要在索引列上做任何计算4. 范围之后全失效5. 尽量使用覆盖索引6. 使用不等于&#xff08;!或<>&#xff09;会使索引失效7. is null 或 is not null也无法使用索引8. like通配符以%开头会使索引失效9. 字符串不加单引号导致索引失…

程序员的悲哀是什么?

点击下方“JavaEdge”&#xff0c;选择“设为星标” 第一时间关注技术干货&#xff01; 免责声明~ 切记&#xff0c;任何文章不要过度深思&#xff08;任何东西都无法经得起审视&#xff0c;因为这世上没有同样的成长环境&#xff0c;也没有同样的认知水平同时也「没有适用于所…

大模型高效训练基础知识:梯度累积(Gradient Accumulationn)

梯度累积 梯度累积&#xff08;Gradient Accumulation&#xff09;的基本思想是将一次性的整批参数更新的梯度计算变为以一小步一小步的方式进行&#xff08;如下图&#xff09;&#xff0c;具体而言该方法以小批次的方式进行模型前向传播和反向传播&#xff0c;过程中迭代计算…