当前位置:   article > 正文

探索Gin框架:快速构建高性能的Golang Web应用

探索Gin框架:快速构建高性能的Golang Web应用

03fabd9f68f94c29b0cb17835ec0fbff.png

前言

12a94581f9f0496e8a400ff0bd3e900e.png

3d97a138168246d78f9bbac2480f71de.png

        Gin框架是一个轻量级的Web框架,基于Go语言开发,旨在提供高性能和简洁的API。它具有快速的路由和中间件支持,使得构建Web应用变得更加简单和高效。无论是构建小型的API服务还是大型的Web应用,Gin框架都能够满足你的需求。

        无论你是一个有经验的开发者,还是一个刚刚入门的初学者,本文都将为你提供清晰的指导和实用的示例代码。无论你是想构建一个简单的API服务,还是一个复杂的Web应用,Gin框架都能够帮助你快速实现你的想法。

目录

​编辑

前言

适用人群

构建第一个Gin应用

1.下载并安装Gin

2.项目导入

3.快速使用示例

路由和中间件

API路由配置

路由分组

静态文件路由设置

静态路径映射

静态文件路由

路由中间件

优雅封装

Gin客户端初始化

定义api路由

在项目入口启动Gin服务

总结


fb11d09ee4f24af2abda2afdd4b8573c.png

适用人群

  • 懂得安装 Go 环境及其基本语法
  • 会使用 Go Modules 管理项目

构建第一个Gin应用

1.下载并安装Gin

go get -u github.com/gin-gonic/gin

2.项目导入

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

3.快速使用示例

  1. package main
  2. import "github.com/gin-gonic/gin"
  3. func main() {
  4. r := gin.Default()
  5. r.GET("/ping", func(c *gin.Context) {
  6. c.JSON(200, gin.H{
  7. "message": "pong",
  8. })
  9. })
  10. r.Run() // 监听并在 0.0.0.0:8080 上启动服务
  11. }

路由和中间件

API路由配置

Gin的API路由配置相当简单,只需要调用对应请求方式的方法,设置请求路径,与请求函数即可

  1. router.GET("/ping", func(c *gin.Context) {
  2. c.JSON(200, gin.H{
  3. "message": "pong",
  4. })
  5. })

路由分组

我们可通过Group方法设置路由分组

  1. // 可使用Group方法设置路由分组
  2. userGroup := router.Group("/user")
  3. // 该接口实际路径为/user/register
  4. userGroup.POST("/register", controller.UserController.Register)
  5. userGroup.POST("/login", controller.UserController.Login)

静态文件路由设置

静态路径映射

router.Static允许我们指定路径映射,如下,当我们访问路径为localhost:8080/storage时,实际上是访问到localhost:8080/storage/app/public

  1. //当访问路径为localhost:8080/storage时,实际上是访问到localhost:8080/storage/app/public
  2. router.Static("/storage", "./storage/app/public")

静态文件路由

设置静态文件夹路由

router.Static("/assets", "./assets")

设置静态文件路由

router.StaticFile("/favicon.ico", "./resources/favicon.ico")

路由中间件

使用use方法可使用gin自带的中间件或者自定义的中间件

我们这里自定义中间件函数,返回类型需为gin.HandlerFunc,这里我们定义三个常用的中间件作为示例

跨域处理中间件

  1. // 跨域处理中间件
  2. func Cors() gin.HandlerFunc {
  3. config := cors.DefaultConfig()
  4. config.AllowAllOrigins = true
  5. config.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization"}
  6. config.AllowCredentials = true
  7. config.ExposeHeaders = []string{"New-Token", "New-Expires-In", "Content-Disposition"}
  8. return cors.New(config)
  9. }

登录认证中间件,这里使用的是JWT认证

  1. // JWTAuth JWT 鉴权中间件
  2. func JWTAuth(GuardName string) gin.HandlerFunc {
  3. return func(c *gin.Context) {
  4. // Token 获取
  5. tokenStr := c.Request.Header.Get("Authorization")
  6. if tokenStr == "" {
  7. response.TokenFail(c)
  8. c.Abort() // 终止请求
  9. return
  10. }
  11. tokenStr = tokenStr[len(service.TokenType)+1:]
  12. // Token 解析校验
  13. token, err := jwt.ParseWithClaims(tokenStr, &service.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
  14. return []byte(global.App.Config.Jwt.Secret), nil
  15. })
  16. // Token 黑名单校验
  17. if err != nil || service.JwtService.IsInBlacklist(tokenStr) {
  18. response.TokenFail(c)
  19. c.Abort()
  20. return
  21. }
  22. // Token 发布者校验和过期校验
  23. claims := token.Claims.(*service.CustomClaims)
  24. if claims.Issuer != GuardName || !token.Valid {
  25. response.TokenFail(c)
  26. c.Abort()
  27. return
  28. }
  29. // token 续签
  30. if claims.ExpiresAt.Time.Unix()-time.Now().Unix() < global.App.Config.Jwt.RefreshGracePeriod {
  31. lock := global.Lock("refresh_token_lock", global.App.Config.Jwt.JwtBlacklistGracePeriod)
  32. if lock.Get() {
  33. err, user := service.JwtService.GetUserInfo(GuardName, claims.ID)
  34. if err != nil {
  35. global.App.Log.Error(err.Error())
  36. lock.Release()
  37. } else {
  38. tokenData, _, _ := service.JwtService.CreateToken(GuardName, user)
  39. c.Header("new-token", tokenData.AccessToken)
  40. c.Header("new-expires-in", strconv.Itoa(tokenData.ExpiresIn))
  41. _ = service.JwtService.JoinBlackList(token)
  42. }
  43. }
  44. }
  45. //将token信息和id信息存入上下文
  46. c.Set("token", token)
  47. c.Set("id", claims.ID)
  48. }
  49. }

gin自带的Recovery中间件默认日志是是打印在控制台的,故使用自定义Recovery中间件来自定义日志输出方式

这些配置信息根据自己情况调整,这里我是通过viper读取配置到全局变量中,后面我应该会出文讲解Go使用Viper读取配置

  1. // CustomRecovery 进行程序的恢复(防止程序因 panic 而终止)和记录错误日志的中间件
  2. func CustomRecovery() gin.HandlerFunc {
  3. // 开启程序的恢复,并将错误日志写入到指定的日志文件中
  4. return gin.RecoveryWithWriter(
  5. &lumberjack.Logger{
  6. // 日志文件名
  7. Filename: global.App.Config.Log.RootDir + "/" + global.App.Config.Log.Filename,
  8. // 文件最大大小
  9. MaxSize: global.App.Config.Log.MaxSize,
  10. // 旧文件的最大个数
  11. MaxBackups: global.App.Config.Log.MaxBackups,
  12. // 旧文件的最大保留天数
  13. MaxAge: global.App.Config.Log.MaxAge,
  14. // 是否压缩
  15. Compress: global.App.Config.Log.Compress,
  16. },
  17. response.ServerError)
  18. }

挂载自定义的中间件,需要注意使用中间件的顺序,gin.Logger()中间件需要放在其他中间件前面,不然可能导致middleware.CustomRecovery()中的日志无法正常使用

  1. router := gin.New()
  2. router.Use(gin.Logger(), middleware.Cors(), middleware.CustomRecovery())

优雅封装

接下来我们优雅的封装Gin的大部分使用

Gin客户端初始化

我们在RunServer()方法中实现了优雅地关闭服务器,当关闭服务器时,如果有请求未结束,会等待5秒,5秒后再关闭服务器

  1. // bootstrap/Router.go
  2. package bootstrap
  3. import (
  4. "context"
  5. "github.com/gin-gonic/gin"
  6. swaggerfiles "github.com/swaggo/files"
  7. ginSwagger "github.com/swaggo/gin-swagger"
  8. "log"
  9. "net/http"
  10. _ "online-practice-system/docs"
  11. "online-practice-system/global"
  12. "online-practice-system/internal/middleware"
  13. "online-practice-system/routes"
  14. "os"
  15. "os/signal"
  16. "syscall"
  17. "time"
  18. )
  19. // 初始化路由
  20. func setupRouter() *gin.Engine {
  21. if global.App.Config.App.Env == "production" {
  22. gin.SetMode(gin.ReleaseMode)
  23. }
  24. router := gin.New()
  25. router.Use(gin.Logger(), middleware.Cors(), middleware.CustomRecovery())
  26. // 前端项目静态资源
  27. router.Static("/storage", "./storage/app/public")
  28. // Swagger 配置
  29. router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
  30. // 注册 api 分组路由
  31. apiGroup := router.Group("/api")
  32. routes.SetUserGroupRoutes(apiGroup)
  33. return router
  34. }
  35. // RunServer 启动服务器
  36. func RunServer() {
  37. r := setupRouter()
  38. //创建一个 http.Server 对象 srv,其中指定服务器的监听地址和路由处理器为之前设置的路由 r
  39. srv := &http.Server{
  40. Addr: ":" + global.App.Config.App.Port,
  41. Handler: r,
  42. }
  43. //使用 srv.ListenAndServe() 方法来异步启动服务器。
  44. go func() {
  45. if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
  46. global.App.Log.Fatal("服务器启动失败:" + err.Error())
  47. }
  48. }()
  49. // 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间), 当收到中断信号时,会触发 quit 通道,从而执行后续的关闭服务器操作。
  50. quit := make(chan os.Signal)
  51. //Notify函数让signal包将输入的中断信号 SIGINT 或终止信号 SIGTERM 转发到通道quit
  52. signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
  53. //收到信号后,会执行下面的代码,首先打印日志,然后调用 srv.Shutdown() 方法来关闭服务器。
  54. <-quit
  55. log.Println("Shutdown Server ...")
  56. //创建一个带有超时的上下文 ctx,超时时间设置为 5 秒
  57. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  58. defer cancel()
  59. //调用 srv.Shutdown() 方法来关闭服务器,此时会触发 http.Server 的关闭事件,从而退出阻塞
  60. if err := srv.Shutdown(ctx); err != nil {
  61. global.App.Log.Fatal("服务器关闭时出现错误:" + err.Error())
  62. }
  63. global.App.Log.Fatal("服务器顺利关闭~~~")
  64. }

定义api路由

  1. // api/Router.go
  2. package routes
  3. import (
  4. "github.com/gin-gonic/gin"
  5. "online-practice-system/internal/controller"
  6. "online-practice-system/internal/middleware"
  7. "online-practice-system/internal/service"
  8. )
  9. // SetUserGroupRoutes 定义 User 分组路由
  10. func SetUserGroupRoutes(router *gin.RouterGroup) {
  11. userGroup := router.Group("/user")
  12. userGroup.POST("/register", controller.UserController.Register)
  13. userGroup.POST("/login", controller.UserController.Login)
  14. //使用 JWTAuth 鉴权中间件
  15. authRouter := userGroup.Use(middleware.JWTAuth(service.AppGuardName))
  16. {
  17. authRouter.GET("/userInfo", controller.UserController.GetUserInfo)
  18. authRouter.GET("/logout", controller.UserController.UserLogout)
  19. authRouter.POST("/image_upload", controller.UploadController.ImageUpload)
  20. authRouter.GET("/image_get_url/:id", controller.UploadController.GetUrlById)
  21. }
  22. }

在项目入口启动Gin服务

  1. package main
  2. import (
  3. "online-practice-system/bootstrap"
  4. )
  5. func main() {
  6. // 启动gin web服务器
  7. bootstrap.RunServer()
  8. }

总结

        感谢您的观看,如果您对gin的使用感兴趣的可以看看我几个月前搭建的go web脚手架,使用了一些主流的开发框架,虽然可能部分设计不是很合理,但是对于我个人来说搭建一般的web项目还是足够了。未使用go-wire的版本:go-web-starter: 基于gin,form框架的web开发脚手架 (gitee.com),使用了go-wire进行全局依赖注入的改造版:go-web-wire-starter: 使用go-wire框架与gin框架搭建的web开发脚手架,有助于web开发者快速开发curd操作,以及学习go-wire框架的工程化实践 (gitee.com)

实现的功能有如下:

• 配置统一管理

• Jwt令牌生成,校验,续签,黑名单

• 定时任务

• 文件存储(支持本地,七牛云Kodo,阿里云Oss,腾讯云Cos等存储服务,支持扩展)

• 分布式锁

• 限流器(基于令牌桶算法)

• 邮件服务

• 自定义命令行命令(代码中以数据库迁移migrate命令为示例)

使用的技术栈如下图:

83f6af6381824b4aad988d8792bb9050.png

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/89931
推荐阅读
  

闽ICP备14008679号