Gin 基础操作
参数解析与校验
解析请求参数、请求体、请求头,Validator 字段校验、自定义校验函数,标准 Rest 响应体等
package main
import (
"errors"
"log/slog"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
)
var (
err error
)
/*
Entity
*/
// 标准Restful响应
type Restful[T any] struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data T `json:"data,omitempty"`
}
// 响应内容
type ResponseBody struct {
UserID int
Username string
SysIP string
SysUser string
}
// 请求体
type RequestBody struct {
SysIP string `json:"sys_ip" binding:"required,ipv4"`
SysUser string `json:"sys_user" binding:"required,enum=root admin user"`
}
// 请求参数
type RequestParams struct {
UserID int `form:"user_id" binding:"required,min=1,max=1000"` // 取值 [1, 1000]
//UserID int `form:"user_id" binding:"required,gt=1,lt=1000"` // 取值 (1, 1000)
}
// 请求头
type RequestHeaders struct {
Authorization string `header:"Authorization"`
}
// BearerToken 从请求头中获取 Bearer token
func (h RequestHeaders) BearerToken(c *gin.Context) (string, error) {
var headers RequestHeaders
if err = c.ShouldBindHeader(&headers); err != nil {
return "", err
}
// 检查 Authorization 字段是否以 "Bearer " 开头
if !strings.HasPrefix(headers.Authorization, "Bearer ") {
return "", errors.New("No Bearer token found in Authorization header")
}
bearerToken := strings.TrimPrefix(headers.Authorization, "Bearer ")
return bearerToken, nil
}
/*
RouterFunc
*/
func HandleRequest(c *gin.Context) {
// 解析请求头
var bearerToken string
bearerToken, err = RequestHeaders{}.BearerToken(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "Invalid request headers", "error": err.Error()})
return
}
// parseJWT 操作
slog.Info(bearerToken, "bearer token")
// 解析请求参数
var query RequestParams
err = c.ShouldBindQuery(&query)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "Invalid request parameters", "error": err.Error()})
return
}
userID := query.UserID
// 解析请求体
var body RequestBody
err = c.ShouldBindJSON(&body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "Invalid request body", "error": err.Error()})
return
}
sysUser := body.SysUser
sysIP := body.SysIP
// 数据库操作
var username string
username, err = GetUserByIDFromDB(userID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "No database entries", "error": err.Error()})
return
}
// 返回响应
c.JSON(http.StatusOK, Restful[ResponseBody]{
Code: 0,
Msg: "success",
Data: ResponseBody{
UserID: userID,
Username: username,
SysIP: sysIP,
SysUser: sysUser,
},
})
}
/*
Main
*/
func main() {
r := Router()
r.Run(":8080")
}
// ----------------------------------------------------------------
/*
Validator
*/
// 自定义Enum类型校验器
func EnumValidator(fl validator.FieldLevel) bool {
validValues := strings.Split(fl.Param(), " ")
value := fl.Field().String()
for _, validValue := range validValues {
if value == validValue {
return true
}
}
return false
}
/*
Router
*/
// Router 返回一个已配置好的 *gin.Engine 实例,用于处理 HTTP 请求路由
func Router() *gin.Engine {
// 注册自定义验证器
v, _ := binding.Validator.Engine().(*validator.Validate)
v.RegisterValidation("enum", EnumValidator)
// 设置 Gin 框架运行模式为发布模式
gin.SetMode(gin.ReleaseMode)
// 创建一个新的 Gin 实例
r := gin.New()
// 启用路径末尾斜杠的重定向(例如:/user/ 重定向到 /user)
r.RedirectTrailingSlash = true
// 处理没有匹配到路由的情况,返回 404 Not Found
r.NoRoute(func(c *gin.Context) {
c.String(http.StatusNotFound, "Not found")
return
})
// 加载中间件
r.Use(gin.Recovery(), PanicHandler())
// 注册路由
r.POST("/user", HandleRequest)
return r
}
/*
Middleware
*/
func PanicHandler() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// 详细的错误分类,分而治之
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"code": 50000,
"msg": "failed",
"error": err,
})
}
}()
c.Next()
}
}
/*
ORM
*/
// 从数据库中查询用户信息
func GetUserByIDFromDB(userID int) (string, error) {
users := map[int]string{
1: "John",
2: "Mary",
3: "David",
40: "Sarah",
50: "Michael",
60: "Emma",
700: "James",
800: "Olivia",
900: "William",
1000: "Sophia",
}
if name, ok := users[userID]; ok {
return name, nil
}
return "", errors.New("No matching user found")
}
中间件加载执行顺序
搞清楚 Gin 中间件的加载顺序非常重要。通过运行下面代码可知,如果我们想要使用 Gin 中间件来完成全局的错误处理,需要将错误处理中间件放在执行链的首位 defer 代码块中。
package main
import (
"fmt"
"log"
"github.com/gin-gonic/gin"
)
func Md1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("--->111")
defer func() {
fmt.Println("<---111")
}()
c.Next()
}
}
func Md2() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("--->222")
defer func() {
fmt.Println("<---222")
}()
c.Next()
}
}
func Md3() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("--->333")
defer func() {
fmt.Println("<---333")
}()
c.Next()
}
}
func main() {
router := gin.Default()
router.Use(Md1(), Md2(), Md3())
router.GET("/", func(c *gin.Context) {
log.Println("hello world")
})
_ = router.Run(":8080")
}
[GIN-debug] Listening and serving HTTP on :8080
--->111
--->222
--->333
2023/09/01 11:47:36 hello world
<---333
<---222
<---111
[GIN] 2023/09/01 - 11:47:36 | 200 | 1.200041ms | 127.0.0.1 | GET "/"
上下文键值对存储
c.Set() 和 c.Request.WithContext(ctx) 的作用是相同的,只是它们涉及到不同的上下文。
c.Set()方法用于在Gin的Context中存储键值对数据,这些数据只在当前请求的处理范围内有效,并且可以通过c.Get()或c.MustGet()方法进行访问和检索。
func(c *gin.Context) {
c.Set("user", &User{...}) // 将信息存储在 Context 中
userinfo := c.MustGet("user").(*User) // 获取存储的信息
}
c.Request.WithContext(ctx)方法用于替换当前请求的上下文,创建一个新的具有指定上下文的请求对象,并将该对象赋值给c.Request。这样做的目的是在整个请求处理过程中传递上下文数据,使数据能够在请求链中的各个处理器和中间件之间共享。
func(c *gin.Context) {
ctx := c.Request.Context()
ctx = context.WithValue(ctx, "user", &User{...}) // 将信息存储在请求的上下文中
c.Request = c.Request.WithContext(ctx)
}