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

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


前言



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


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

适用人群

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

构建第一个Gin应用

1.下载并安装Gin

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

2.项目导入

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

3.快速使用示例

package main
 
import "github.com/gin-gonic/gin"
 
func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{
      "message": "pong",
    })
  })
  r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

路由和中间件

API路由配置

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

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

路由分组

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

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


静态文件路由设置

静态路径映射

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

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

静态文件路由

设置静态文件夹路由

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


设置静态文件路由

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

路由中间件

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


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

跨域处理中间件

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


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

// JWTAuth JWT 鉴权中间件
func JWTAuth(GuardName string) gin.HandlerFunc {
  return func(c *gin.Context) {
    // Token 获取
    tokenStr := c.Request.Header.Get("Authorization")
    if tokenStr == "" {
      response.TokenFail(c)
      c.Abort() // 终止请求
      return
    }
    tokenStr = tokenStr[len(service.TokenType)+1:]
    // Token 解析校验
    token, err := jwt.ParseWithClaims(tokenStr, &service.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
      return []byte(global.App.Config.Jwt.Secret), nil
    })
    // Token 黑名单校验
    if err != nil || service.JwtService.IsInBlacklist(tokenStr) {
      response.TokenFail(c)
      c.Abort()
      return
    }
    // Token 发布者校验和过期校验
    claims := token.Claims.(*service.CustomClaims)
    if claims.Issuer != GuardName || !token.Valid {
      response.TokenFail(c)
      c.Abort()
      return
    }
    // token 续签
    if claims.ExpiresAt.Time.Unix()-time.Now().Unix() < global.App.Config.Jwt.RefreshGracePeriod {
      lock := global.Lock("refresh_token_lock", global.App.Config.Jwt.JwtBlacklistGracePeriod)
      if lock.Get() {
        err, user := service.JwtService.GetUserInfo(GuardName, claims.ID)
        if err != nil {
          global.App.Log.Error(err.Error())
          lock.Release()
        } else {
          tokenData, _, _ := service.JwtService.CreateToken(GuardName, user)
          c.Header("new-token", tokenData.AccessToken)
          c.Header("new-expires-in", strconv.Itoa(tokenData.ExpiresIn))
          _ = service.JwtService.JoinBlackList(token)
        }
      }
    }
    //将token信息和id信息存入上下文
    c.Set("token", token)
    c.Set("id", claims.ID)
  }
}

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

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

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

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

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

优雅封装

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

Gin客户端初始化

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

// bootstrap/Router.go
package bootstrap
import (
  "context"
  "github.com/gin-gonic/gin"
  swaggerfiles "github.com/swaggo/files"
  ginSwagger "github.com/swaggo/gin-swagger"
  "log"
  "net/http"
  _ "online-practice-system/docs"
  "online-practice-system/global"
  "online-practice-system/internal/middleware"
  "online-practice-system/routes"
  "os"
  "os/signal"
  "syscall"
  "time"
)
// 初始化路由
func setupRouter() *gin.Engine {
  if global.App.Config.App.Env == "production" {
    gin.SetMode(gin.ReleaseMode)
  }
  router := gin.New()
  router.Use(gin.Logger(), middleware.Cors(), middleware.CustomRecovery())
  // 前端项目静态资源
  router.Static("/storage", "./storage/app/public")
  // Swagger 配置
  router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
  // 注册 api 分组路由
  apiGroup := router.Group("/api")
  routes.SetUserGroupRoutes(apiGroup)
  return router
}
// RunServer 启动服务器
func RunServer() {
  r := setupRouter()
  //创建一个 http.Server 对象 srv,其中指定服务器的监听地址和路由处理器为之前设置的路由 r
  srv := &http.Server{
    Addr:    ":" + global.App.Config.App.Port,
    Handler: r,
  }
  //使用 srv.ListenAndServe() 方法来异步启动服务器。
  go func() {
    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
      global.App.Log.Fatal("服务器启动失败:" + err.Error())
    }
  }()
  // 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间), 当收到中断信号时,会触发 quit 通道,从而执行后续的关闭服务器操作。
  quit := make(chan os.Signal)
  //Notify函数让signal包将输入的中断信号 SIGINT 或终止信号 SIGTERM 转发到通道quit
  signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
  //收到信号后,会执行下面的代码,首先打印日志,然后调用 srv.Shutdown() 方法来关闭服务器。
  <-quit
  log.Println("Shutdown Server ...")
  //创建一个带有超时的上下文 ctx,超时时间设置为 5 秒
  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  defer cancel()
  //调用 srv.Shutdown() 方法来关闭服务器,此时会触发 http.Server 的关闭事件,从而退出阻塞
  if err := srv.Shutdown(ctx); err != nil {
    global.App.Log.Fatal("服务器关闭时出现错误:" + err.Error())
  }
  global.App.Log.Fatal("服务器顺利关闭~~~")
}

定义api路由

// api/Router.go
package routes
import (
  "github.com/gin-gonic/gin"
  "online-practice-system/internal/controller"
  "online-practice-system/internal/middleware"
  "online-practice-system/internal/service"
)
// SetUserGroupRoutes 定义 User 分组路由
func SetUserGroupRoutes(router *gin.RouterGroup) {
  userGroup := router.Group("/user")
  userGroup.POST("/register", controller.UserController.Register)
  userGroup.POST("/login", controller.UserController.Login)
  //使用 JWTAuth 鉴权中间件
  authRouter := userGroup.Use(middleware.JWTAuth(service.AppGuardName))
  {
    authRouter.GET("/userInfo", controller.UserController.GetUserInfo)
    authRouter.GET("/logout", controller.UserController.UserLogout)
    authRouter.POST("/image_upload", controller.UploadController.ImageUpload)
    authRouter.GET("/image_get_url/:id", controller.UploadController.GetUrlById)
  }
}

在项目入口启动Gin服务

package main
 
import (
  "online-practice-system/bootstrap"
)
 
func main() {
  // 启动gin web服务器
  bootstrap.RunServer()
}


总结

   感谢您的观看,如果您对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命令为示例)使用的技术栈如下图:

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2天前
|
SQL 安全 数据库
如何构建一个安全的Web应用:技术与策略的全面指南
【6月更文挑战第12天】构建安全Web应用的全面指南:了解SQL注入、XSS等威胁,采用输入验证、安全编程语言,配置安全服务器和数据库,使用HTTPS,实施会话管理、访问控制,正确处理错误和日志,定期进行安全审计和漏洞扫描。确保用户数据和应用安全。
|
3天前
|
中间件 API Go
使用Echo和Gin构建高性能Web服务的技术文档
本文档对比了Go语言中的两个流行Web框架——Echo和Gin。Echo是一个高性能、可扩展的框架,适合构建微服务和API,强调简洁API和并发性能。Gin基于net/http包,具有Martini风格API,以其快速路由和丰富社区支持闻名。在性能方面,Gin的路由性能出色,两者并发性能均强,内存占用低。文中还提供了使用Echo和Gin构建Web服务的代码示例,帮助开发者了解如何运用这两个框架。选择框架应考虑项目需求和个人喜好。
15 2
|
15天前
|
缓存 自然语言处理 数据库
构建高效Python Web应用:异步编程与Tornado框架
【5月更文挑战第30天】在追求高性能Web应用开发的时代,异步编程已成为提升响应速度和处理并发请求的关键手段。本文将深入探讨Python世界中的异步编程技术,特别是Tornado框架如何利用非阻塞I/O和事件循环机制来优化Web服务的性能。我们将剖析Tornado的核心组件,并通过实例演示如何构建一个高效的Web服务。
|
3天前
|
开发框架 Dart JavaScript
深入探讨Flutter中的Web支持功能,以及如何利用Flutter构建跨平台Web应用的最佳实践
【6月更文挑战第11天】Flutter,Google的开源跨平台框架,已延伸至Web支持,让开发者能用同一代码库构建移动和Web应用。Flutter Web基于Dart转JavaScript,利用WebAssembly和JavaScript在Web上运行。构建Web应用最佳实践包括选择合适项目、优化性能、进行兼容性测试和利用Flutter的声明式UI、热重载等优势。尽管性能挑战存在,Flutter Web为跨平台开发提供了更多机会和潜力。
33 1
|
3天前
|
运维 Serverless API
Serverless 应用引擎产品使用合集之如何实现一键迁移Web框架
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
4天前
|
Java Serverless 应用服务中间件
Serverless 应用引擎产品使用合集之Web函数启动的Spring Boot项目可以通过什么方式配置Nginx
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
5天前
|
缓存 前端开发 数据库
构建高性能Web应用的关键技术
本文将介绍构建高性能Web应用所需的关键技术。我们将探讨前端、后端、数据库等多个方面的技术,并提供实用的建议,帮助开发者优化应用性能并提升用户体验。
|
5天前
|
前端开发 测试技术 数据库
Ruby on Rails:快速开发Web应用的秘密
【6月更文挑战第9天】Ruby on Rails,一款基于Ruby的Web开发框架,以其高效、简洁和强大备受青睐。通过“约定优于配置”减少配置工作,内置丰富功能库加速开发,如路由、数据库访问。活跃的社区和海量资源提供支持,MVC架构与RESTful设计确保代码清晰可扩展。高效的数据库迁移和测试工具保证质量。Rails是快速构建Web应用的理想选择,未来将持续影响Web开发领域。
|
10天前
|
存储 数据库 开发者
IndexedDB解密:打开Web应用的数据存储之门
IndexedDB解密:打开Web应用的数据存储之门
37 0
|
14天前
|
XML 网络协议 Java
XML Web 服务技术解析:WSDL 与 SOAP 原理、应用案例一览
XML Web服务是基于WSDL、SOAP、RDF和RSS等标准的网络应用程序组件技术。WSDL描述服务接口和消息格式,SOAP用于结构化信息交换,RDF描述网络资源,RSS则用于发布网站更新。Web服务特点是自包含、自描述,基于开放协议,可重用且能连接现有软件。WSDL文档包含`types`、`message`、`portType`和`binding`元素,定义服务操作和协议。SOAP协议规定消息格式,通过HTTP等传输。
476 1