Gin框架

原生net/http库开发痛点

1、参数解析与验证繁琐,需要手动解析URL参数、表单数据、JSON数据等

2、路由不直观,没有内置路径参数支持,需要手动解析URL路径

3、响应处理比较原始,需要手动序列化JSON、设置响应头等

4、缺少中间件机制,日志、鉴权、限流等功能需要自行实现

5、错误处理分散,没有统一的错误管理机制

Gin框架简介

Gin 是 Go 语言生态中最受欢迎的轻量级高性能 Web 框架,基于 httprouter 实现,路由采用基数树(Radix Tree)数据结构,提供 Martini-like API 但性能提升 40 倍以上。适合构建 RESTful API 和微服务。

核心特性:

  • 零分配路由:极其高效的路由匹配,无堆内存分配
  • 中间件支持:可扩展的中间件系统(洋葱模型)
  • Crash-free:内置 Recovery 中间件防止 panic 导致服务崩溃
  • JSON 验证:自动请求/响应 JSON 绑定与验证
  • 路由分组:组织相关路由并应用公共中间件
  • 内置渲染:支持 JSON、XML、YAML、HTML 模板等多种响应格式

一、入门示例

1、设置环境及初始化

D:\go\opsAdminPermission> go env -w GO111MODULE=on # 启用 Go Modules 功能
D:\go\opsAdminPermission> go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct #加速
D:\go\opsAdminPermission> go mod init  opsAdminPermission #初始化项目,创建go.mod文件
D:\go\opsAdminPermission> go mod tidy #增加丢失的module,去掉未用的module

2、gin框架基本请求示例

1、使用gin的Default方法创建一个路由Handler
2、通过http方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把request、response都封装到了gin.Context的上下文环境中
3、最后启动路由的Run方法监听端口,或者自定义http服务器配置启动都可以
package main

import (
	"net/http"

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

// gin框架入门
func main() {
	//默认初始化,返回gin的引擎
	r := gin.Default()
	//绑定路由规则及函数
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "pong",
		})
	})
	r.POST("/upload", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "upload success",
		})
	})
	r.PUT("/put", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "put success",
		})
	})
	r.DELETE("/delete", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "deleted success",
		})
	})
	//绑定端口,启动监听
	r.Run(":8080")
}

3、路由分组

package main

import (
	"net/http"

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

func main() {
	r := gin.Default()
	//路由分组示例
	rg := r.Group("/api/user")
	{
		rg.POST("/add", useradd)
		rg.DELETE("/delete", userdel)
	}
	r.Run(":8080")
}

func userdel(c *gin.Context) {}
func useradd(c *gin.Context) {}

4、获取路径请求参数

4.1、单个url参数
package main

import (
	"net/http"

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

// gin框架入门
func main() {
	r := gin.Default()
	rg := r.Group("/api/users")
	rg.GET("/:id", func(ctx *gin.Context) {
		id := ctx.Param("id")
		ctx.JSON(http.StatusOK, gin.H{
			"id": id,
		})
	})
	r.Run(":8080")
}
4.2、多个url参数
package main

import (
	"net/http"

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

type Param struct {
	Name string `uri:"name"`
	Age  int    `uri:"age"`
}

// gin框架入门
func main() {
	r := gin.Default()
	rg := r.Group("/api/users")

	rg.GET("/:name/:age", func(ctx *gin.Context) {
		var param Param
		err := ctx.ShouldBindUri(&param)
		if err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{
				"msg": "参数非法",
			})
			return
		}
		ctx.JSON(http.StatusOK, gin.H{
			"name": param.Name,
			"age":  param.Age,
		})
	})
	r.Run(":8080")
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5、获取get和post参数

5.1、获取get请求参数(Query参数)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package main

import (
	"net/http"

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

// 获取用户从前端传过来的参数 URL中携带?问号拼接的这种
func main() {
	r := gin.Default()
	rg := r.Group("/api/users")
	// http://localhost:8080/api/users/detail?name=csdn&id=29
	rg.GET("/search", func(ctx *gin.Context) {
		nameQuery := ctx.Query("name")
		idQuery := ctx.Query("id")
		ctx.JSON(http.StatusOK, gin.H{
			"name": nameQuery, //给前端展示回去
			"id":   idQuery,
		})
	})
	r.Run(":8080")
}

使用 ShouldBindQuery 绑定结构体:

package main

import (
	"net/http"

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

// 获取用户从前端传过来的参数 ?问号拼接的这种
type Query struct {
	Id   int    `form:"id"`
	Name string `form:"name"`
}

func main() {
	r := gin.Default()
	rg := r.Group("/api/users")
	// http://localhost:8080/api/users/detail?name=csdn&id=29
	rg.GET("/search", func(ctx *gin.Context) {
		var query Query
		err := ctx.ShouldBindQuery(&query)
		if err != nil {
			ctx.JSON(400, gin.H{
				"msg": "参数非法", //给前端返回错误
			})
			return
		}
		ctx.JSON(http.StatusOK, gin.H{
			"name": query.Name, //给前端展示回去
			"id":   query.Id,
		})
	})
	r.Run(":8080")
}
5.2、获取post请求参数
post请求form-data参数示例

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package main

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

func main() {
	r := gin.Default()
	rg := r.Group("/api/users")
	rg.POST("/list", func(ctx *gin.Context) {
		id := ctx.PostForm("id")
		ctx.JSON(200, gin.H{
			"id": id,
		})
	})
	r.Run(":8081")
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package main

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

type Query struct {
	Id int `form:"id" json:"id"`
}

func main() {
	r := gin.Default()
	rg := r.Group("/api/users")
	rg.POST("/list", func(ctx *gin.Context) {
		var a Query
		err := ctx.ShouldBind(&a) //自动判断传过来的参数类型
		if err != nil {
			return
		}
		ctx.JSON(200, gin.H{
			"id": a.Id,
		})
	})
	r.Run(":8081")
}

6、返回值

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package main

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

type Query struct {
	Id int `form:"id" json:"id"`
}

func main() {
	r := gin.Default()
	rg := r.Group("/api/users")
	rg.GET("/list", func(ctx *gin.Context) {
		//string方式返回
		ctx.String(200, "请求成功")
		//json方式返回
		ctx.JSON(200, gin.H{
			"msg": "11",
		})
		//结构体返回
		ctx.JSON(200, Query{
			Id: 1,
		})
	})
	r.Run(":8081")
}

7、validator校验包

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

// 参数必填、密码长度及复杂度校验
type Query struct {
	Name string `json:"name" validate:"required"`
	Pwd  string `json:"pwd" validate:"required,min=6,max=15"`
}

func main() {
	//实例化结构体
	req := &Query{
		Name: "admin",
		Pwd:  "12345",
	}
	//创建一个新的 validator 实例,用于验证结构体字段。
	validate := validator.New()
	//调用 validate.Struct(req) 来对 req 进行验证
	err := validate.Struct(req)
	if err != nil {
		//err断言
		for _, e := range err.(validator.ValidationErrors) {
			fmt.Println("===========", e)
		}
		return
	}
	fmt.Println("------------")
}
//如下所示 当pwd为5位时,运行代码提示不符合pwd的最短密码位数规则

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

7.1、自定义验证规则

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

// 参数必填、密码长度及复杂度校验
type Query struct {
	Name string `json:"name" validate:"required,CustomValidationErrors"`
	Pwd  string `json:"pwd" validate:"required,min=6,max=15"`
}

func main() {
	req := &Query{
		Name: "admi",
		Pwd:  "12345",
	}
	validate := validator.New()
	//validator.RegisterValidation 接收三个参数:tag、fn 和 callValidationEvenIfNull
	/*
		tag:自定义验证规则的名称,你用来标识验证规则
		fn:验证函数,类型是 validator.Func,该函数接受一个 validator.FieldLevel 类型的参数并返回一个布尔值,表示验证是否通过
		callValidationEvenIfNull:一个可选参数,布尔类型。默认情况下,如果字段值为 nil 或空值,验证器将跳过该字段的验证。如果设置为 true,即使字段为空,验证器仍然会调用你的自定义验证函数
	*/
	_ = validate.RegisterValidation("CustomValidationErrors", CustomValidationErrors)
	err := validate.Struct(req)
	if err != nil {
		for _, e := range err.(validator.ValidationErrors) {
			fmt.Println("===========", e)
		}
		return
	}
	fmt.Println("------------")
}

func CustomValidationErrors(fl validator.FieldLevel) bool {
	//如果字段的值不是 "admin",则返回 false,表示验证失败
	return fl.Field().String() != "admin"
}
7.2、将错误翻译成中文

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package main

import (
	"fmt"
	"reflect"

	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	zh_translation "github.com/go-playground/validator/v10/translations/zh"
)

// 参数必填、密码长度及复杂度校验
type Query struct {
	Name string `json:"name" validate:"required,CustomValidationErrors"`
	Pwd  string `json:"pwd" validate:"required,min=6,max=15" label:"密码"`
}

func main() {
	req := &Query{
		Name: "admin",
		Pwd:  "12345",
	}
	validate := validator.New()
	uni := ut.New(zh.New())
	//拿到翻译器
	trans, _ := uni.GetTranslator("zh")
	err := zh_translation.RegisterDefaultTranslations(validate, trans)
	if err != nil {
		fmt.Println(err)
		return
	}
	//获取结构体中字段的标签名称,前提是字段中配置了label标签,这样就能通过Translate获取到字段名称翻译
	validate.RegisterTagNameFunc(func(field reflect.StructField) string {
		return field.Tag.Get("label")
	})
	//validator.RegisterValidation 接收三个参数:tag、fn 和 callValidationEvenIfNull
	/*
		tag:自定义验证规则的名称,你用来标识验证规则
		fn:验证函数,类型是 validator.Func,该函数接受一个 validator.FieldLevel 类型的参数并返回一个布尔值,表示验证是否通过
		callValidationEvenIfNull:一个可选参数,布尔类型。默认情况下,如果字段值为 nil 或空值,验证器将跳过该字段的验证。如果设置为 true,即使字段为空,验证器仍然会调用你的自定义验证函数
	*/
	_ = validate.RegisterValidation("CustomValidationErrors", CustomValidationErrors)
	err = validate.Struct(req)
	if err != nil {
		for _, e := range err.(validator.ValidationErrors) {
			fmt.Println("===========", e.Translate(trans)) //将错误翻译为中文
		}
		return
	}
	fmt.Println("------------")
}

func CustomValidationErrors(fl validator.FieldLevel) bool {
	//如果字段的值不是 "admin",则返回 false,表示验证失败,反之为成功
	return fl.Field().String() == "admin"
}
7.3、gin框架中把错误翻译成中文

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package main

import (
	"errors"
	"fmt"
	"reflect"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/en"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	en_translation "github.com/go-playground/validator/v10/translations/en"
	zh_translation "github.com/go-playground/validator/v10/translations/zh"
)

//定义一个全局变量 trans,类型为 ut.Translator,它用于存储翻译器实例

var trans ut.Translator

// 初始化翻译转换器
func InitTrans(local string) error {
	//通过类型断言 (*validator.Validate),将验证引擎强制转换为 *validator.Validate 类型
	var err error
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		//处理自定义的tag label:名称
		v.RegisterTagNameFunc(func(fld reflect.StructField) string {
			return fld.Tag.Get("label")
		})
		//注册翻译器
		uni := ut.New(en.New(), zh.New(), en.New())
		//返回的是一个 ut.Translator 类型的实例,它将用于将验证错误信息翻译成目标语言
		var ok bool
		trans, ok = uni.GetTranslator(local)
		if !ok {
			return fmt.Errorf("uni.getTrans(%s) failed", local)
		}
		switch local {
		case "en":
			err = en_translation.RegisterDefaultTranslations(v, trans)
		case "zh":
			err = zh_translation.RegisterDefaultTranslations(v, trans)
		default:
			err = en_translation.RegisterDefaultTranslations(v, trans)
		}
		return err
	}
	return errors.New("binding.validator failed")
}

func FormatValidatorErr(fields map[string]string) string {
	var str string
	for _, err := range fields {
		fmt.Println(fields) //map[Req.年龄:年龄为必填字段] key  Req.年龄 value 年龄为必填字段
		str = err
		break
	}
	return str
}

func main() {
	if err := InitTrans("zh"); err != nil {
		panic(err)
	}
	r := gin.Default()
	rg := r.Group("/api/users")
	rg.POST("/list", func(ctx *gin.Context) {
		type Req struct {
			Name string `json:"name" binding:"required" label:"姓名"`
			Age  int    `json:"age"  binding:"required" label:"年龄"`
		}
		var req Req
		err := ctx.ShouldBind(&req)
		if err != nil {
			errs, ok := err.(validator.ValidationErrors)
			if !ok {
				ctx.JSON(200, gin.H{
					"msg": "参数错误",
				})
				return
			}
			fmt.Println(errs.Translate(trans))
			ctx.JSON(200, gin.H{
				//errs.Translate(trans)实际返回ValidationErrorsTranslations类型,但它的类型为map[string]string 刚好切合FormatValidatorErr()函数的参数
				"msg": FormatValidatorErr(errs.Translate(trans)), //返回一个翻译后的错误信息,以便返回给客户端
			})
			return
		}
		ctx.JSON(200, gin.H{"msg": "success"})
	})
	r.Run(":8081")
}
7.4、常用Validator验证规则速查表
Tag 说明 示例
required 必填字段 validate:"required"
min / max 数值/字符串长度范围 validate:"min=6,max=15"
len 字符串长度等于指定值 validate:"len=11"
eq / ne 等于/不等于 validate:"eq=admin"
gt / gte 大于/大于等于 validate:"gt=0"
lt / lte 小于/小于等于 validate:"lt=100"
email 邮箱格式 validate:"email"
url URL格式 validate:"url"
oneof 枚举值 validate:"oneof=male female"
numeric 数字字符串 validate:"numeric"
alpha 纯字母 validate:"alpha"
alphanum 字母+数字 validate:"alphanum"
ip IP地址 validate:"ip"

8、post几种绑定参数的方式

1、直接使用golang封装好的状态码,在请求后返回对应的状态
2、请求统一返回200状态码,然后代码中自行添加错误码code,根据code码排错
c.JSON(200,gin.H{
	"msg":  "success",
	"code": 20000,
})
package main

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

func main() {
	r := gin.Default()
	rg := r.Group("/api/users")
	rg.POST("/list", func(ctx *gin.Context) {
		type Req struct {
			Name string `json:"name" binding:"required"`
			Age  int    `json:"age" binding:"required"`
		}
		var req Req
		// MustBindWith 如果绑定失败,会直接返回400状态码
		// err := ctx.MustBindWith(&req, binding.JSON)

		err := ctx.ShouldBind(&req)
		if err != nil {
			ctx.JSON(200, gin.H{
				"error": err.Error(),
				"code":  40000,
			})
			return
		}
		ctx.JSON(200, gin.H{
			"msg":  "success",
			"code": 20000,
		})
	})
	r.Run(":8081")
}

9、响应

返回字符串
func main() {
	r := gin.Default()
	r.GET("/ping", func(ctx *gin.Context) {
		ctx.String(200, "pong")
	})
	r.Run(":8080")
}
返回JSON
func main() {
	//创建默认路由
	r := gin.Default()
	//绑定路由规则及函数
	r.POST("/upload", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "upload success",
		})
	})
}

二、gin中间件

1、中间件引入

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package main

import (
	"fmt"
	"time"

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

func main() {
	r := gin.New()
	//全局中间件
	r.Use(gin.Logger())
	//recovery中间件会recover任何panic,如果有panic的话会写入500
	r.Use(gin.Recovery())
	r.GET("/user", MyLogger(), func(ctx *gin.Context) {
		fmt.Println("当代码运行到下方的ctx.Next()时,会先执行此处的func(ctx *gin.Context),之后才会执行ctx.Next()下方剩余的代码")
	})
	r.Run(":8081")
}

func MyLogger() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		t := time.Now().UnixMilli()
		ctx.Set("token", "123")
		ctx.Next()
		latency := time.Now().UnixMilli() - t
		fmt.Printf("耗时: %d\n", latency)
		//ctx.Next() 如果在此处,则最后才会执行上方GET请求中的func(ctx *gin.Context)回调函数
	}
}

2、Next和Abort

  • ctx.Next():执行后续中间件和处理函数,执行完后再返回当前中间件继续执行 ctx.Next() 之后的代码(洋葱模型)
  • ctx.Abort():阻止后续中间件和处理函数执行,但不会停止当前函数,只会跳过后续链上的函数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package main

import (
	"fmt"
	"time"

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

func main() {
	r := gin.New()
	//全局中间件
	r.Use(gin.Logger())
	//recovery中间件会recover任何panic,如果有panic的话会写入500
	r.Use(gin.Recovery())
	r.GET("/user", MyLogger(), func(ctx *gin.Context) {
		//不会执行到此回调函数,因为ctx.Abort()阻止了后续执行
	})
	r.Run(":8081")
}

func MyLogger() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		//阻止挂起的函数,不会停止当前的函数,会停止下一次要请求的函数
		//在此处,不会执行上述GET请求中的func(ctx *gin.Context)回调函数
		ctx.Abort()

		t := time.Now().UnixMilli()
		ctx.Set("token", "123")
		//执行请求(执行下一次的请求)
		// ctx.Next()
		latency := time.Now().UnixMilli() - t
		fmt.Printf("耗时: %d\n", latency)
	}
}

3、中间件数据传递

通过 ctx.Set(key, value)ctx.Get(key) 在中间件和处理函数之间共享数据:

func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("userID", 1001)
		c.Next()
	}
}

func main() {
	r := gin.Default()
	r.GET("/profile", AuthMiddleware(), func(c *gin.Context) {
		userID, _ := c.Get("userID")
		c.JSON(200, gin.H{"userID": userID})
	})
	r.Run(":8081")
}

4、AbortWithStatusJSON

中间件中可以直接终止请求并返回JSON响应:

func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.GetHeader("Authorization")
		if token == "" {
			c.AbortWithStatusJSON(401, gin.H{"error": "未登录"})
			return
		}
		c.Next()
	}
}

5、常用第三方中间件

中间件 功能 导入路径
CORS 跨域处理 github.com/gin-contrib/cors
Sessions 会话管理 github.com/gin-contrib/sessions
Gzip 响应压缩 github.com/gin-contrib/gzip
Cache 缓存 github.com/gin-contrib/cache
PProf 性能分析 github.com/gin-contrib/pprof
RequestID 请求ID github.com/gin-contrib/requestid
Zap 结构化日志 github.com/gin-contrib/zap

三、优雅的重启和退出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"time"

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

func main() {
	r := gin.Default()
	r.GET("/", func(ctx *gin.Context) {
		ctx.JSON(200, gin.H{
			"msg": "success",
		})
	})
	// 创建 HTTP Server
	srv := &http.Server{
		Addr:    ":8081",
		Handler: r,
	}
	//开启协程
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			fmt.Printf("listen: %s\n", err)
		}
	}()
	// 创建无缓冲通道,发送和接收操作都是阻塞的
	// 等待中断信号
	// kill 默认会发送 syscall.SIGTERM 信号
	// kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号
	// kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它
	// signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt)

	// 从quit通道中读取信号
	// 阻塞在此,当接收到上述两种信号时才会往下执行
	<-quit
	fmt.Println("shutdown server....")

	// 创建一个 5 秒的超时上下文
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// 关闭 HTTP Server
	// 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出
	if err := srv.Shutdown(ctx); err != nil {
		fmt.Println("Server Shutdown:", err)
	}
	fmt.Println("Server exiting")
}

四、跨域问题解决(CORS)

1、什么是跨域

浏览器同源策略限制:协议、域名、端口任一不同即为跨域。前端 http://localhost:3000 请求后端 http://localhost:8081 时,浏览器会拦截响应。

2、手动设置CORS中间件

package main

import (
	"net/http"

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

// Cors 自定义跨域中间件
func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		c.Header("Access-Control-Allow-Origin", "*") // 允许所有域名,生产环境应指定具体域名
		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
		c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, Token")
		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
		c.Header("Access-Control-Allow-Credentials", "true")
		c.Header("Access-Control-Max-Age", "86400") // 预检请求缓存时间(秒)

		// OPTIONS 预检请求直接返回 204
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}
		c.Next()
	}
}

func main() {
	r := gin.Default()
	r.Use(Cors()) // 全局启用跨域

	r.GET("/api/users", func(c *gin.Context) {
		c.JSON(200, gin.H{"msg": "success"})
	})
	r.Run(":8081")
}

3、使用gin-contrib/cors中间件(推荐)

package main

import (
	"time"

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

func main() {
	r := gin.Default()

	// 配置CORS
	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"http://localhost:3000", "https://example.com"}, // 允许的域名
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "Content-Type", "Authorization", "Token"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,           // 是否允许携带Cookie
		AllowOriginFunc: func(origin string) bool {
			return origin == "http://localhost:3000"
		},
		MaxAge: 12 * time.Hour, // 预检请求缓存时间
	}))

	r.GET("/api/users", func(c *gin.Context) {
		c.JSON(200, gin.H{"msg": "success"})
	})
	r.Run(":8081")
}

五、文件上传与下载

1、单文件上传

package main

import (
	"fmt"
	"net/http"
	"path/filepath"

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

func main() {
	r := gin.Default()

	// 设置上传文件大小限制(8MB)
	r.MaxMultipartMemory = 8 << 20

	r.POST("/upload", func(c *gin.Context) {
		// 从表单中获取文件,参数名 "file" 对应前端的 name="file"
		file, err := c.FormFile("file")
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "文件上传失败: " + err.Error()})
			return
		}

		// 保存文件到指定路径
		dst := filepath.Join("./uploads", file.Filename)
		if err := c.SaveUploadedFile(file, dst); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "文件保存失败"})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"msg":      "上传成功",
			"filename": file.Filename,
			"size":     file.Size,
		})
	})
	r.Run(":8081")
}

2、多文件上传

r.POST("/upload-multiple", func(c *gin.Context) {
	form, err := c.MultipartForm()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
		return
	}

	files := form.File["files"] // 前端 input 的 name="files" 且设置 multiple
	var uploadedFiles []string

	for _, file := range files {
		dst := filepath.Join("./uploads", file.Filename)
		if err := c.SaveUploadedFile(file, dst); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("文件 %s 保存失败", file.Filename)})
			return
		}
		uploadedFiles = append(uploadedFiles, file.Filename)
	}

	c.JSON(http.StatusOK, gin.H{
		"msg":   "批量上传成功",
		"files": uploadedFiles,
	})
})

3、文件下载

r.GET("/download/:filename", func(c *gin.Context) {
	filename := c.Param("filename")
	filePath := filepath.Join("./uploads", filename)

	// 检查文件是否存在
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
		return
	}

	// c.File 直接返回文件(浏览器预览)
	// c.File(filePath)

	// c.FileAttachment 以附件形式下载
	c.FileAttachment(filePath, filename)
})

六、静态文件服务与HTML模板渲染

1、静态文件服务

func main() {
	r := gin.Default()

	// 将 /static 路径映射到 ./public 目录
	// 如访问 http://localhost:8080/static/js/app.js 对应 ./public/js/app.js
	r.Static("/static", "./public")

	// 将单个文件映射到指定路由
	r.StaticFile("/favicon.ico", "./public/favicon.ico")

	r.Run(":8080")
}

2、HTML模板渲染

目录结构:

project/
├── main.go
└── templates/
    ├── index.html
    └── user/
        └── profile.html

加载模板:

package main

import (
	"net/http"

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

func main() {
	r := gin.Default()

	// 加载 templates 目录下所有模板文件
	r.LoadHTMLGlob("templates/**/*")

	// 或者只加载指定模式的文件
	// r.LoadHTMLGlob("templates/*.html")

	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", gin.H{
			"title": "首页",
			"name":  "Gin框架",
		})
	})

	r.GET("/user/:name", func(c *gin.Context) {
		name := c.Param("name")
		c.HTML(http.StatusOK, "user/profile.html", gin.H{
			"name": name,
			"age":  25,
		})
	})

	r.Run(":8080")
}

templates/index.html:

<!DOCTYPE html>
<html>
<head>
    <title>{{ .title }}</title>
</head>
<body>
    <h1>欢迎来到 {{ .name }}</h1>
</body>
</html>

3、模板自定义函数

func main() {
	r := gin.Default()

	// 注册自定义模板函数
	r.SetFuncMap(template.FuncMap{
		"formatTime": func(t time.Time) string {
			return t.Format("2006-01-02 15:04:05")
		},
	})

	r.LoadHTMLGlob("templates/**/*")
	// ...
}

七、路由进阶

1、支持全部HTTP方法 — Any

// Any 匹配 GET、POST、PUT、PATCH、HEAD、OPTIONS、DELETE、CONNECT、TRACE 所有请求方法
r.Any("/api/any", func(c *gin.Context) {
	c.JSON(200, gin.H{
		"method": c.Request.Method,
	})
})

2、处理不存在的路由 — NoRoute

r.NoRoute(func(c *gin.Context) {
	c.JSON(http.StatusNotFound, gin.H{
		"code": 404,
		"msg":  "页面不存在",
	})
})

3、处理不支持的方法 — NoMethod

r.NoMethod(func(c *gin.Context) {
	c.JSON(http.StatusMethodNotAllowed, gin.H{
		"code": 405,
		"msg":  "请求方法不允许",
	})
})

4、路由重定向

// HTTP重定向
r.GET("/old", func(c *gin.Context) {
	c.Redirect(http.StatusMovedPermanently, "/new")
})

// 路由内跳转(HandleContext)
r.GET("/jump", func(c *gin.Context) {
	c.Request.URL.Path = "/target"
	r.HandleContext(c)
})

r.GET("/target", func(c *gin.Context) {
	c.JSON(200, gin.H{"msg": "target route"})
})

5、路由组嵌套与中间件结合

func main() {
	r := gin.Default()

	// 公开路由组
	public := r.Group("/api/public")
	{
		public.GET("/ping", func(c *gin.Context) { c.String(200, "pong") })
	}

	// 需要认证的路由组
	private := r.Group("/api/admin")
	private.Use(AuthMiddleware())
	{
		private.GET("/dashboard", adminDashboard)
		// 嵌套子路由组也自动继承父组中间件
		users := private.Group("/users")
		{
			users.GET("/", listUsers)
			users.POST("/", createUser)
		}
	}

	r.Run(":8080")
}

八、数据绑定详解

1、各种绑定方法对比

方法 说明 失败时行为
c.ShouldBind(&obj) 根据Content-Type自动选择绑定方式 返回错误
c.ShouldBindJSON(&obj) 绑定JSON 返回错误
c.ShouldBindXML(&obj) 绑定XML 返回错误
c.ShouldBindQuery(&obj) 绑定查询参数(Query) 返回错误
c.ShouldBindUri(&obj) 绑定路径参数 返回错误
c.ShouldBindYAML(&obj) 绑定YAML 返回错误
c.ShouldBindHeader(&obj) 绑定请求头 返回错误
c.MustBind(&obj) 同ShouldBind,失败直接返回400 直接返回400
c.MustBindWith(&obj, bindingType) 指定绑定类型,失败直接返回400 直接返回400

2、JSON绑定示例

type User struct {
	Name  string `json:"name" binding:"required"`
	Email string `json:"email" binding:"required,email"`
	Age   int    `json:"age" binding:"gte=0,lte=130"`
}

r.POST("/user", func(c *gin.Context) {
	var user User
	if err := c.ShouldBindJSON(&user); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	c.JSON(200, gin.H{"user": user})
})

3、Form表单绑定

type LoginForm struct {
	Username string `form:"username" binding:"required"`
	Password string `form:"password" binding:"required,min=6"`
}

r.POST("/login", func(c *gin.Context) {
	var form LoginForm
	if err := c.ShouldBind(&form); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	// 业务逻辑...
	c.JSON(200, gin.H{"msg": "登录成功"})
})

4、请求头绑定

type Headers struct {
	Token       string `header:"Authorization" binding:"required"`
	ContentType string `header:"Content-Type"`
}

r.GET("/header", func(c *gin.Context) {
	var h Headers
	if err := c.ShouldBindHeader(&h); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	c.JSON(200, gin.H{"token": h.Token})
})

九、日志系统

1、默认日志中间件

func main() {
	r := gin.Default() // 默认已经包含了 Logger() 和 Recovery() 中间件

	// 等效于:
	// r := gin.New()
	// r.Use(gin.Logger())
	// r.Use(gin.Recovery())

	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})
	r.Run(":8080")
}

2、将日志写入文件

func main() {
	// 禁用控制台颜色
	gin.DisableConsoleColor()

	// 创建日志文件
	f, _ := os.Create("gin.log")
	// 将日志同时写入文件和控制台
	gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})
	r.Run(":8080")
}

3、自定义日志格式

func main() {
	// 自定义日志格式
	r := gin.New()
	r.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
		return fmt.Sprintf("[GIN] %s | %d | %s | %s | %s | %s\n",
			params.TimeStamp.Format("2006-01-02 15:04:05"),
			params.StatusCode,
			params.Latency,
			params.ClientIP,
			params.Method,
			params.Path,
		)
	}))
	r.Use(gin.Recovery())

	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})
	r.Run(":8080")
}

4、LogColor 彩色日志

// 启用彩色日志
gin.ForceConsoleColor()

十、Cookie与Session

1、Cookie操作

func main() {
	r := gin.Default()

	// 设置Cookie
	r.GET("/set-cookie", func(c *gin.Context) {
		c.SetCookie("username", "zhangsan", 3600, "/", "localhost", false, true)
		// SetCookie(name, value, maxAge(秒), path, domain, secure, httpOnly)
		c.String(200, "Cookie设置成功")
	})

	// 读取Cookie
	r.GET("/get-cookie", func(c *gin.Context) {
		username, err := c.Cookie("username")
		if err != nil {
			c.String(400, "Cookie未找到")
			return
		}
		c.String(200, "username: %s", username)
	})

	// 删除Cookie
	r.GET("/del-cookie", func(c *gin.Context) {
		c.SetCookie("username", "", -1, "/", "localhost", false, true)
		c.String(200, "Cookie已删除")
	})

	r.Run(":8080")
}

2、Session使用(gin-contrib/sessions)

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// 创建基于Cookie的session存储(生产环境建议用Redis存储)
	store := cookie.NewStore([]byte("secret-key"))
	r.Use(sessions.Sessions("mysession", store))

	// 设置Session
	r.POST("/login", func(c *gin.Context) {
		session := sessions.Default(c)
		session.Set("userID", 1001)
		session.Save()
		c.JSON(200, gin.H{"msg": "登录成功"})
	})

	// 读取Session
	r.GET("/profile", func(c *gin.Context) {
		session := sessions.Default(c)
		userID := session.Get("userID")
		if userID == nil {
			c.JSON(401, gin.H{"error": "未登录"})
			return
		}
		c.JSON(200, gin.H{"userID": userID})
	})

	// 清除Session
	r.POST("/logout", func(c *gin.Context) {
		session := sessions.Default(c)
		session.Clear()
		session.Save()
		c.JSON(200, gin.H{"msg": "已退出"})
	})

	r.Run(":8080")
}

十一、Gin项目结构最佳实践

以下是推荐的标准项目目录结构:

project/
├── main.go                 # 入口文件
├── config/                 # 配置文件
│   └── config.go
├── router/                 # 路由定义
│   └── router.go
├── middleware/              # 中间件
│   ├── auth.go
│   ├── cors.go
│   └── logger.go
├── controller/             # 控制器层(处理请求)
│   ├── user_controller.go
│   └── order_controller.go
├── service/                # 业务逻辑层
│   ├── user_service.go
│   └── order_service.go
├── model/                  # 数据模型
│   ├── user.go
│   └── order.go
├── dao/                    # 数据访问层
│   ├── user_dao.go
│   └── order_dao.go
├── dto/                    # 数据传输对象(请求/响应结构体)
│   ├── user_dto.go
│   └── order_dto.go
├── common/                 # 公共工具
│   ├── response.go         # 统一响应格式
│   ├── error_code.go       # 错误码定义
│   └── validator.go        # 验证器初始化
└── utils/                  # 工具函数
    └── helper.go

统一响应格式示例

// common/response.go
package common

import (
	"net/http"

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

type Response struct {
	Code int         `json:"code"`
	Msg  string      `json:"msg"`
	Data interface{} `json:"data,omitempty"`
}

func Success(c *gin.Context, data interface{}) {
	c.JSON(http.StatusOK, Response{
		Code: 20000,
		Msg:  "success",
		Data: data,
	})
}

func Error(c *gin.Context, code int, msg string) {
	c.JSON(http.StatusOK, Response{
		Code: code,
		Msg:  msg,
	})
}

十二、GORM数据库集成

GORM 是 Go 语言中最流行的 ORM 库,与 Gin 配合使用非常常见。

1、安装

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql   # MySQL驱动
go get -u gorm.io/driver/postgres # PostgreSQL驱动

2、数据库初始化和连接

package main

import (
	"fmt"
	"log"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var DB *gorm.DB

func InitDB() {
	dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
	var err error
	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info), // 打印SQL日志
	})
	if err != nil {
		log.Fatal("数据库连接失败:", err)
	}

	// 设置连接池
	sqlDB, _ := DB.DB()
	sqlDB.SetMaxIdleConns(10)
	sqlDB.SetMaxOpenConns(100)
	sqlDB.SetConnMaxLifetime(time.Hour)

	fmt.Println("数据库连接成功")
}

3、模型定义与迁移

// model/user.go
type User struct {
	ID        uint      `gorm:"primaryKey" json:"id"`
	Name      string    `gorm:"size:50;not null" json:"name"`
	Email     string    `gorm:"size:100;uniqueIndex" json:"email"`
	Age       int       `gorm:"default:0" json:"age"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

// 自动迁移
func InitDB() {
	// ...连接数据库
	DB.AutoMigrate(&User{}) // 自动创建/更新表结构
}

4、Gin + GORM 完整CRUD示例

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var DB *gorm.DB

type User struct {
	ID    uint   `json:"id" gorm:"primaryKey"`
	Name  string `json:"name" gorm:"size:50;not null"`
	Email string `json:"email" gorm:"size:100;uniqueIndex"`
}

func main() {
	// 连接数据库
	dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True"
	DB, _ = gorm.Open(mysql.Open(dsn), &gorm.Config{})
	DB.AutoMigrate(&User{})

	r := gin.Default()
	rg := r.Group("/api/users")
	{
		rg.POST("/", createUser)
		rg.GET("/", listUsers)
		rg.GET("/:id", getUser)
		rg.PUT("/:id", updateUser)
		rg.DELETE("/:id", deleteUser)
	}
	r.Run(":8080")
}

func createUser(c *gin.Context) {
	var user User
	if err := c.ShouldBindJSON(&user); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	DB.Create(&user)
	c.JSON(201, gin.H{"data": user, "msg": "创建成功"})
}

func listUsers(c *gin.Context) {
	var users []User
	DB.Find(&users)
	c.JSON(200, gin.H{"data": users})
}

func getUser(c *gin.Context) {
	id := c.Param("id")
	var user User
	if err := DB.First(&user, id).Error; err != nil {
		c.JSON(404, gin.H{"error": "用户不存在"})
		return
	}
	c.JSON(200, gin.H{"data": user})
}

func updateUser(c *gin.Context) {
	id := c.Param("id")
	var user User
	if err := DB.First(&user, id).Error; err != nil {
		c.JSON(404, gin.H{"error": "用户不存在"})
		return
	}
	if err := c.ShouldBindJSON(&user); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	DB.Save(&user)
	c.JSON(200, gin.H{"data": user, "msg": "更新成功"})
}

func deleteUser(c *gin.Context) {
	id := c.Param("id")
	if err := DB.Delete(&User{}, id).Error; err != nil {
		c.JSON(500, gin.H{"error": "删除失败"})
		return
	}
	c.JSON(200, gin.H{"msg": "删除成功"})
}

5、GORM常用查询

// 条件查询
DB.Where("name = ?", "zhangsan").First(&user)
DB.Where("age > ?", 18).Find(&users)
DB.Where("name LIKE ?", "%zhang%").Find(&users)
DB.Where("age BETWEEN ? AND ?", 18, 30).Find(&users)

// 分页查询
var page, pageSize = 1, 10
DB.Offset((page - 1) * pageSize).Limit(pageSize).Find(&users)

// 排序
DB.Order("age desc").Find(&users)

// 链式组合
DB.Where("age > ?", 18).Order("id desc").Limit(10).Find(&users)

// 统计
var count int64
DB.Model(&User{}).Where("age > ?", 18).Count(&count)

十三、Swagger API文档生成

1、安装

go get -u github.com/swaggo/swag/cmd/swag
go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files

2、添加API注释

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	swaggerFiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"

	_ "your-project/docs" // swag init 生成的docs包
)

// @title           Gin框架API文档
// @version         1.0
// @description     这是一个Gin框架的示例API文档
// @host            localhost:8080
// @BasePath        /api/v1

// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization

func main() {
	r := gin.Default()

	// Swagger文档路由
	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

	v1 := r.Group("/api/v1")
	{
		v1.GET("/users", GetUsers)
		v1.POST("/users", CreateUser)
	}

	r.Run(":8080")
}

// GetUsers 获取用户列表
// @Summary      获取用户列表
// @Description  返回所有用户信息
// @Tags         用户管理
// @Accept       json
// @Produce      json
// @Param        page  query    int     false  "页码"  default(1)
// @Param        size  query    int     false  "每页数量" default(10)
// @Success      200   {object}  common.Response{data=[]User}
// @Router       /users [get]
func GetUsers(c *gin.Context) {
	// ...
	var users []User
	c.JSON(http.StatusOK, gin.H{"data": users})
}

// CreateUser 创建用户
// @Summary      创建用户
// @Description  创建一个新用户
// @Tags         用户管理
// @Accept       json
// @Produce      json
// @Param        user  body      User  true  "用户信息"
// @Success      201   {object}  common.Response
// @Failure      400   {object}  common.Response
// @Security     Bearer
// @Router       /users [post]
func CreateUser(c *gin.Context) {
	// ...
}

3、生成文档

# 在项目根目录执行(main.go 所在目录)
swag init

# 如果main.go在cmd目录下
swag init -g cmd/main.go

生成后访问 http://localhost:8080/swagger/index.html 即可查看API文档。


十四、单元测试

1、创建测试文件

// main_test.go
package main

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

// 初始化测试路由
func setupRouter() *gin.Engine {
	gin.SetMode(gin.TestMode) // 使用测试模式
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{"message": "pong"})
	})
	r.GET("/user/:id", func(c *gin.Context) {
		id := c.Param("id")
		c.JSON(200, gin.H{"id": id})
	})
	r.POST("/login", func(c *gin.Context) {
		type Req struct {
			Name string `json:"name" binding:"required"`
		}
		var req Req
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(400, gin.H{"error": err.Error()})
			return
		}
		c.JSON(200, gin.H{"name": req.Name})
	})
	return r
}

// 测试GET请求
func TestPingRoute(t *testing.T) {
	router := setupRouter()

	req, _ := http.NewRequest("GET", "/ping", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	assert.Contains(t, w.Body.String(), "pong")
}

// 测试路径参数
func TestUserRoute(t *testing.T) {
	router := setupRouter()

	req, _ := http.NewRequest("GET", "/user/123", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	assert.Contains(t, w.Body.String(), "123")
}

// 测试POST请求
func TestLoginRoute(t *testing.T) {
	router := setupRouter()

	body := strings.NewReader(`{"name":"zhangsan"}`)
	req, _ := http.NewRequest("POST", "/login", body)
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	assert.Contains(t, w.Body.String(), "zhangsan")
}

// 运行测试: go test -v

2、运行测试命令

# 运行所有测试
go test ./... -v

# 运行指定测试函数
go test -run TestPingRoute -v

# 查看测试覆盖率
go test -cover

# 生成覆盖率报告
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

十五、常见问题与性能优化

1、gin.Default() vs gin.New()

方法 说明
gin.Default() 创建带 Logger 和 Recovery 中间件的引擎
gin.New() 创建空白引擎,不包含任何中间件,适用于自定义中间件场景

2、ShouldBind vs MustBind

  • ShouldBind:绑定失败返回 error,由开发者自行处理
  • MustBind:绑定失败直接返回 400 错误并终止请求链

3、绑定JSON时常见问题

// 错误:前端传JSON,后端用了form标签
type Req struct {
	Name string `form:"name"` // 错误!JSON请求应该用json标签
}

// 正确:JSON绑定使用json标签
type Req struct {
	Name string `json:"name" binding:"required"`
}

4、性能优化建议

  1. 生产环境设置Gin模式
gin.SetMode(gin.ReleaseMode)
  1. 设置请求体大小限制
r.MaxMultipartMemory = 8 << 20 // 8MB
  1. 连接池配置(使用GORM时)
sqlDB, _ := DB.DB()
sqlDB.SetMaxIdleConns(10)     // 空闲连接数
sqlDB.SetMaxOpenConns(100)    // 最大连接数
sqlDB.SetConnMaxLifetime(time.Hour)
  1. 合理使用中间件:全局中间件会影响所有路由,只在全局层面添加必要的中间件(如日志、恢复),特定路由的中间件使用组级别注册

  2. 避免在循环中进行数据库查询:使用批量查询替代N+1查询

  3. 使用context超时控制

ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
defer cancel()
DB.WithContext(ctx).Find(&users)

5、常见错误处理模式

// 统一错误处理包裹函数
func wrapHandler(fn func(*gin.Context) error) gin.HandlerFunc {
	return func(c *gin.Context) {
		if err := fn(c); err != nil {
			c.JSON(500, gin.H{"error": err.Error()})
		}
	}
}

// 使用示例
r.GET("/api", wrapHandler(func(c *gin.Context) error {
	result, err := someBusinessLogic()
	if err != nil {
		return err
	}
	c.JSON(200, result)
	return nil
}))

本文档持续更新中,如有遗漏或错误欢迎补充修正。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐