前言
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命令为示例)使用的技术栈如下图: