GO学习之 微框架(Gin)

GO系列

1、GO学习之Hello World
2、GO学习之入门语法
3、GO学习之切片操作
4、GO学习之 Map 操作
5、GO学习之 结构体 操作
6、GO学习之 通道(Channel)
7、GO学习之 多线程(goroutine)
8、GO学习之 函数(Function)
9、GO学习之 接口(Interface)
10、GO学习之 网络通信(Net/Http)
11、GO学习之 微框架(Gin)
12、GO学习之 数据库(mysql)
13、GO学习之 数据库(Redis)
14、GO学习之 搜索引擎(ElasticSearch)
15、GO学习之 消息队列(Kafka)
16、GO学习之 远程过程调用(RPC)
17、GO学习之 goroutine的调度原理
18、GO学习之 通道(nil Channel妙用)
19、GO学习之 同步操作sync包
20、GO学习之 互斥锁、读写锁该如何取舍
21、GO学习之 条件变量 sync.Cond
22、GO学习之 单例模式 sync.Once
23、GO 面试题总结一【面试官这样问】

前言

按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
说起微服务框架,首当其冲的就是 JAVA 技术栈的 Spring Cloud 全家桶,吃过的人都说好,比较 Spring Cloud 已经可以说是完全能满足互联网后端开发需求,用 Spring Cloud 来搭建一个三高(高并发、高可用、高性能)的后端架构并非难事。
Spring Cloud:Zuul(路由)、Gateway(路由)、Eureka(注册中心)、Hystrix(熔断限流)、Config(配置中心)、Bus(事件,消息总线)、Sleuth(日志收集,链路追踪)、Ribbon(负载均衡)、OpenFeign(API 调用) 等。
Spring Cloud AlibabaNacos(注册配置中心)Sentinel(限流降级)、Seata(分布式事务)、Stream(分布式消息)等。
那 Go 语言自然也会包容微服务的思想,Gin 就是 Go 语言中的一个微服务框架,是一个轻量级的、高性能的 Web 框架,为专门构建快速的 Web 应用和 API 而设计。

一、Gin 简介

  • Gin 是一个轻量级的、高性能的 Web 框架,专门为构建快速的 Web 应用和 API 而设计。它是基于 Go 语言的标准库,提供了简单易用的 API 和许多有用的功能,开发者可以快速地构建和部署 Web 服务。
  • 强大的路由和中间件支持,Gin 提供了灵活的路由定义和中间件机制,可以方便的处理各种负责的逻辑。
  • 适用于构建 RESTful API,Gin 提供了 JSON 构建和解析响应功能,非常适合构建 RESTful 风格 API。

当然也有不足之处:

  • 功能相对简化,相比一些成熟的 Web 框架,Gin 在一些方面较为简化,如模板渲染等。
  • 社区相对较小,虽然 Gin 在国内拥有一定的用户群体,但是相对 Spring 等其他 Web 框架,社区还是相对较小的。

二、Gin 框架搭建

在使用 Gin 框架之前,首先需要安装 Gin 包,就类似 JAVA 中如果要用到 web 模块就需要映入 starter-web 模块。

2.1 安装 Gin

通过如下命令来安装:

go get github.com/gin-gonic/gin

安装成功

2.2 简单示例

下面是一个基于 Gin 写的一个 简单的 API 示例。

示例中,首先导入 “github.com/gin-gonic/gin” 包,并使用 gin.Default() 来创建一个默认的 Gin 引擎 router。然后使用 router 来定义了一个 路由,使用匿名函数来处理响应,返回了一个 Hello Gin 的相应字符串。最后使用 router.Run() 启动这个 HTTP 服务。

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	// 创建一个 Gin 引擎
	router := gin.Default()

	// 定义路由和处理函数,这里使用匿名函数
	router.GET("/", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "Hello Gin!!!")
	})

	// 启动 HTTP 服务器
	router.Run("127.0.0.1:8080")
}

我们使用 go run hello.go 来运行,浏览器访问测试:
访问成功

三、路由和处理函数

3.1 什么是路由?

路由是指根据客户端请求的 URL 路径,将请求映射到相应的处理函数上,这里对比 SpringMVC 框架的 DispatcherServlet 控制中心。在 Web 应用中,当用户访问不同的 URL 路径时,应用程序就需要调用相对于的处理函数来处理用户的请求,每个请求的处理函数都有不同的业务逻辑,路由就会帮你将不同的请求分配到正确的处理函数上

在 Gin 框架中,你可以使用不同的 HTTP 方法(GET, POST, PUT, DELETE等)来定义不同的路由,并且为每一个路由指定一个处理函数。

3.2 Gin 框架中的路由及案例

Gin 框架中采用的路由是基于 httprouter 做的。
下面来看一个小小的示例:

下面示例中,通过 Gin 框架定义了 /hello GET 接口和 /add POST 接口,并且从请求体中获取 name 参数。
我来来看,一个包中,我们可以定义不同路由已完成不同的请求操作。

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	// 创建一个 Gin 引擎
	router := gin.Default()

	// 定义路由和处理函数,这里使用匿名函数
	router.GET("/hello", func(ctx *gin.Context) {
		// 从请求体中查询 name 参数
		name := ctx.Query("name")
		fmt.Printf("接收到参数:%s", name)
		ctx.String(http.StatusOK, "Hello Gin! %s", name)
	})

	router.POST("/add", func(ctx *gin.Context) {
		var requestData struct {
			Name string "json:name binding:required"
		}
		// 使用 ShouldBindJSON 方法将请求体中的 JSON 数据绑定到结构体中
		if err := ctx.ShouldBindJSON(&requestData); err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{"error:": err.Error()})
			return
		}
		// 从结构体中获取参数
		name := requestData.Name
		if len(name) == 0 {
			fmt.Println("获取name参数失败!")
			ctx.String(http.StatusBadRequest, "add fail!")
		} else {
			fmt.Printf("添加操作:%+v 完成\n", name)
			ctx.String(http.StatusOK, "%s add successful!", name)
		}
	})
	// 启动 HTTP 服务器
	router.Run("127.0.0.1:8080")
}

我们 Postman 来请求测试:
/hello GET 接口:
GET请求
/add POST 请求:
POST请求

3.3 实际开发中的应用及优缺点

在实际开发中,路由和函数处理运用非常广泛,以下是一些例子:

  1. 页面访问:给页面提供不同的 API 接口以供页面获取数据渲染,当用户访问不同的页面或资源时,通过 URL 统一资源定位符 来调用不同的路由获取资源。
  2. API 路由:除了前端页面,其他客户端比如:小程序、APP、给第三方系统提供接口等。

优点:

  • 组织结构清晰:路由可以将不同的请求和处理函数组织起来,以便代码更加清晰。
  • 模块化开发:可以实现模块化开发,每个处理函数对应每个路由处理不同的业务请求。
  • 灵活性:路由可以通过不同的 URL 路径调用处理不同的处理函数,实现灵活处理请求。

缺点:

  • 路由管理:如果规模庞大,路由管理可能变的复杂,需要合理地组织维护。
  • 路由冲突:在定义路由时,如果定义了相同的路由,可能会导致路由冲突。
  • 可读性:如果路由过多或者命名不清晰,可能会降低代码的可读性。

四、请求和响应处理

在 Gin 框架中,请求和响应处理是开发 Web 应用的核心部分,也是程序猿日常做的最多的事情。
每开发一个API,逃不过这几步骤:

4.1 接受参数

  • 获取 Request Header 参数
token := ctx.GetHeader("token")
  • 获取 Request Path 参数
id := ctx.Param("id")
  • 获取 Request Param 参数
name := ctx.Query("name")
  • 获取 Request Body 参数

在获取 RequestBody 参数是,我们需要封装结构体,然后绑定请求体到结构体中。

var requestData struct {
	Name string "json:name binding:required"
}
// 使用 ShouldBindJSON 方法将请求体中的 JSON 数据绑定到结构体中
if err := ctx.ShouldBindJSON(&requestData); err != nil {
	ctx.JSON(http.StatusBadRequest, gin.H{"error:": err.Error()})
	return
}
// 从结构体中获取参数
name := requestData.Name

4.2 处理请求

在路由处理函数中,你可以执行你想要的处理逻辑。例如,你可以查询数据库、调用其他函数、处理数据等等。处理请求的逻辑在路由处理函数中实现,声明处理函数或者使用匿名函数来处理。

4.3 生成响应

目前大多数的 Web 应用开发或者是系统之间相互调用、传参、接受数据等大多都是 JSON 格式数据,那在 SpringMVC 框架中有做封装,那在 Gin 框架中也是如此。

下面的案例中,我们通过 ctx.JSON() 方法向前端以 json 格式把数据返回,你可以将响应的数据封装成一个 gin.H(类似 map)对象,然后使用 ctx.JSON 方法将其作为响应的主体返回给客户端。

注意: 这里碰到一个小坑,忘记了 Go 语法中,小写是没有导出权限的,所以导致 postman 访问,data 中总是 [] 的,询问 大佬(chatGPT)才知道其中之奥妙,需大写

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	// 创建一个 Gin 引擎
	router := gin.Default()

	// 创建一个 Get 请求
	router.GET("/test/:id", func(ctx *gin.Context) {
		id := ctx.Param("id")
		name := ctx.Query("name")
		data := struct {
			// 注意,这里定义字段一定要大写,要不然则不能导出,返回空
			Id   interface{} `json:"id"`
			Name string      `json:"name"`
		}{
			Id:   id,
			Name: name,
		}
		ctx.JSON(http.StatusOK, gin.H{"code": 200, "message": "请求成功", "data": data})
	})

	// 启动服务
	router.Run(":8080")
}

在 postma 中测试

调用:http://127.0.01:8080/test/10?name=phen
响应:
{
    "code": 200,
    "data": {
        "id": "10",
        "name": "phen"
    },
    "message": "请求成功"
}

当然,如果需要返回其他类型的响应,比如 HTML页面、纯文本等,可以使用 ctx.HTMLctx.String等方法,根据需要,设置适当的状态码和响应头。

五、中间件

5.1 什么是中间件(Middleware)?

  • 中间件(Middleware)是一种常见的软件设计模式,在 Web 开发中用于请求和响应之间添加自定义逻辑。
  • 中间件可以在请求到达处理函数之前或者在响应客户端之前,对请求和响应做处理。
  • 在 Gin 框架中,中间件是一种非常重要的概念,用于处理一些通用的操作。
  • 如果知道 SpringMVC框架的话,就会知道 Filter 过滤器,Filter 有 Pre-proces 和 Post-process 操作,Gin 框架的中间件和 Filter 差不多。

5.2 中间件的作用及案例

中间件的作用对于请求和响应的进行预处理、后处理和记录,以便实现如下功能(不限于):

  1. 身份验证和授权:中间件中进行用户身份验证和授权,确保用户能够访问特定资源。
  2. 日志记录:可以在中间件中记录 PV、用户操作记录等便于监控和排错。
  3. 请求参数验证:在中间件中可以进行参数验证、XSS 攻击过滤等,确保用户提交的订单数据合法。
  4. 缓存和性能优化:可以在中间件中进行热数据的缓存,减少后端数据库的压力。
  5. 防爬机制:可以在中间件中对请求参数等进行校验,以防别人通过接口爬数据。
  6. 请求耗时统计:中间件中记录请求耗时,以便性能分析。

如何使用中间件呢:

5.2.1 全局中间件

在上面的案例中加入了中间件的使用,在下面的案例中,定义了一个 Logger() 函数来记录请求日志,在 Logger() 函数中,首先记录请求请求进入时间,调用 Next() 函数 继续处理请求 最后记录结束记录时间和耗时。

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

// 定义中间件处理函数,用户记录日志
func Logger() gin.HandlerFunc {
	// 使用自定义函数来处理
	return func(ctx *gin.Context) {
		fmt.Println("Middleware Logger Handler...")
		start := time.Now()
		// 调用 Next 继续处理请求
		ctx.Next()
		end := time.Now()
		latency := end.Sub(start)
		log.Printf("[%s] 请求方法:%s 请求路径:%s 耗时:%v", end.Format("2006-01-02 15:04:05"), ctx.Request.Method, ctx.Request.URL.Path, latency)
	}
}

func main() {
	// 创建一个 Gin 引擎
	router := gin.Default()
	
	// 添加自定义的 Logger 中间件
	router.Use(Logger())

	// 定义路由和处理函数,这里使用匿名函数
	router.GET("/hello", func(ctx *gin.Context) {
		// 从请求体中查询 name 参数
		name := ctx.Query("name")
		fmt.Printf("请求处理...接收到参数:%s \n", name)
		ctx.String(http.StatusOK, "Hello Gin! %s", name)
	})

	router.POST("/add", func(ctx *gin.Context) {
		var requestData struct {
			Name string "json:name binding:required"
		}
		// 使用 ShouldBindJSON 方法将请求体中的 JSON 数据绑定到结构体中
		if err := ctx.ShouldBindJSON(&requestData); err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{"error:": err.Error()})
			return
		}
		// 从结构体中获取参数
		name := requestData.Name
		if len(name) == 0 {
			fmt.Println("获取name参数失败!")
			ctx.String(http.StatusBadRequest, "add fail!")
		} else {
			fmt.Printf("添加操作:%+v 完成\n", name)
			ctx.String(http.StatusOK, "%s add successful!", name)
		}
	})

	// 启动 HTTP 服务器
	router.Run("127.0.0.1:8080")
}

通过 Postman 请求测试:
中间件处理
从上面的截图中可以看处,每次请求都调用到了中间件 Logger()。

5.2.2 局部中间件

上面是全局中间件,所有请求都会进入中间件,但是有时候并非所有请求都需要中间件处理,这时候就需要某几个特殊的 API 接口实现中间件即可。

package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

// 定义局部中间件函数,检查用户权限
func checkAuth() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		token := ctx.Query("token")
		if len(token) == 0 {
			ctx.Error(errors.New("无权访问"))
			ctx.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "无权访问"})
		} else {
			log.Printf("请求方法:%s 请求路径:%s 正常访问", ctx.Request.Method, ctx.Request.URL.Path)
		}
	}
}

func main() {
	// 创建一个 Gin 引擎
	router := gin.Default()

	router.GET("/list", checkAuth(), func(ctx *gin.Context) {
		errors := ctx.Errors
		if errors != nil {
			ctx.Errors = nil
			return
		}
		data := struct {
			// 注意,这里定义字段一定要大写,要不然则不能导出,返回空
			Id   interface{} `json:"id"`
			Name string      `json:"name"`
		}{
			Id:   1,
			Name: "phen",
		}
		fmt.Println("data: ", data)
		ctx.JSON(http.StatusOK, gin.H{"code": 200, "message": "请求成功", "data": data})
	})

	// 启动 HTTP 服务器
	router.Run("127.0.0.1:8080")
}

通过浏览器访问测试:
访问成功:
访问成功
无权访问:
无权访问
从两张截图中可以看出,有 token 则访问成功,没 token 则人访问失败!

注意、注意、注意:
这里遇到一个问题,第一次测试是我的代码中没有 ctx.Errors = nil 这句代码,就发现如下情况:

  • 第一次访问不带 token 参数,自然无权访问
  • 第二次访问带上 token, 没有无权访问,但是也没有访问成功结果
  • 为啥呢?chatGPT 告诉我

因为 Gin 框架上下文在一个请求中是共享的,所以在同一个请求的不同处理函数中,上下文中的数据和异常信息是可以共享的。
这种设计有助于在请求的不同处理阶段中传递数据和异常信息,但也需要小心处理,以确保不会导致意外的结果。如果你想要在每次请求中使用一个干净的上下文,可以在每个请求处理函数的开头重新初始化上下文,或者在处理函数之间隔离上下文。
如果你不希望在不同请求之间共享异常信息,你可以在每次请求开始时,将上下文清空或初始化,以确保上下文不会受到之前请求的影响。

所以我加入了这句代码 ctx.Errors = nil,每次访问过之后清楚调异常信息,则每次访问的时候,带 token 则访问成功,不带则无权访问,这样的处理对于新手的我来说应该不是很合理,后面找大佬咨询,或者哪位大佬看到了,评论区指导一下

5.3 中间件的优缺点

有点:

  • 代码复用:中间件可以通过将公共的代码逻辑提取出来,进行封装,实现代码的复用,避免相似逻辑和重复代码。
  • 逻辑解耦:中间件可以将请求和处理逻辑解耦,是的代码更加清晰、模块化和易于维护。
  • 灵活性:中间件可以在不同的路由器上使用,在上面的案例中,/hello GET 和 /add POST 路由都使用了 Logger() 中间件。

缺点:

  • 复杂性:通过中间件处理,可能会增加业务的复杂性,不当使用会导致业务代码难以理解。
  • 性能影响:每次请求都需要经过中间件,这样中间件中逻辑如果过于复杂,可能影响请求API性能。

六、错误处理

Gin 框架中提供了一种简便的方式来处理函数中发生的异常,确保错误能够捕获并且正确的返回给前端。

6.1 上下文存储异常信息

在下面的案例中,定义了一个 failFunc() 函数,此函数的作用就是返回一个异常,当在 Get() 方法中获取到的 err 时,则用 ctx.Error(err) 将错误信息存储在 Gin 上下文中。这样,Gin 框架会在请求处理完毕后,检查是否有存储的异常信息,如果有则把错误信息返回给前端。

package main

import (
	"errors"
	"net/http"

	"github.com/gin-gonic/gin"
)

func failFunc() error {
	return errors.New("运行异常")
}

func main() {
	// 创建一个 Gin 引擎
	router := gin.Default()

	// 创建一个 Get 请求
	router.GET("/test/:id", func(ctx *gin.Context) {
		err := failFunc()
		if err != nil {
			// 将错误信息存储在 Gin 上下文中
			ctx.Error(err)
			ctx.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "服务器异常"})
			return
		}

		ctx.JSON(http.StatusOK, gin.H{"code": 200, "message": "请求成功"})
	})

	// 启动服务
	router.Run("127.0.0.1:8080")
}

通过 postman 调用测试:

请求:http://127.0.01:8080/test/10?name=phen
响应:
{
    "code": 500,
    "message": "服务器异常"
}

不过我发现,如果在出现异常是,也就是 err 不为空是,里面的 return 不要,则后面所有的 ctx.JSONctx.String等,都会成功放回,所以请求结果会如下:

请求:http://127.0.01:8080/test/10?name=phen
响应:
{
    "code": 500,
    "message": "服务器异常"
}{
    "code": 200,
    "message": "请求成功"
}请求结束

这一点不会像 Spring框架中,response 过之后,如果再 response 则会报错。

总之,Gin 框架通过在 Gin 上下文中存储错误信息的方式,提供了方法处理错误的机制,使得处理函数中的错误被捕获并且能够返回给前端。
这样有助于用户体验和调试信息。

6.2 获取异常信息

而也可以通过 ctx.Errors 获取到所有运行中的异常信息

fmt.Println("运行时的所有异常:", ctx.Errors)

只要在 return 前通过 Errors() 方法获取存储在 Gin 中的异常信息,就可以拿到所有的运行过程中的异常,以便业务逻辑的判断和处理。

七、总结

什么是微框架(Gin),Gin 框架是一个轻量级的,高性能的 Web 框架,专门为构架快速的 Web 应用和 API 而设计。它是基于 Go 语言的标准库开发,提供简单易用的 API 和其他更多的功能,开发者可以快速的构建出 Web 应用并且部署。
通过 go get github.com/gin-gonic/gin 来安装 Gin 框架到开发者环境,通过 框架提供的 路由和处理函数来快速开发 API,接受参数通过函数处理业务逻辑,然后通过 Gin 框架提供的 JSON、String、HTML等方法返回给前端不同类型数据。
通过中间件来处理公共的业务逻辑,比如认证授权、日志记录等。
也有异常处理机制,Gin 通过上下文存储异常信息,然后把异常信息返回给前端来实现异常的处理机制,提高用户体验和调试信息。
最后,Gin 框架总算是有些入门了,后面继续学习框架其他内容,然后博客分享。


现阶段还是对 Go 语言的学习阶段,想必有一些地方考虑的不全面,本文示例全部是亲自手敲代码并且执行通过。
如有问题,还请指教。
评论去告诉我哦!!!一起学习一起进步!!!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值