推荐阅读官方文档 , 里面写的很详细

安装 gin

万物始于安装, 首先是安装 gin, 只需要执行下面一条指令即可:

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

如果你出现了报错的信息, 可以试试修改 go 的代理, 即下面这条指令:

go env -w GOPROXY=https://goproxy.cn,direct

在你的项目中导入 gin:

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

执行以下代码, 在浏览器中输入 localhost:8090/ping, 出现 pong 且后台不报错, 即证明安装成功

package main

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

func main() {
	// 创建一个默认的路由引擎
	r := gin.Default()
    // 设置请求路径为 /ping, 请求方式为 get
    r.GET("/ping", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "pong")
	})
	// 启动HTTP服务,默认在8080端口启动服务, 这里指定为8090端口
	r.Run(":8090")
}

返回数据

上述测试代码中的 ctx.String() 代表 gin 会给前端返回一个 string 类型的数据, 括号内的参数依次为 http 状态码和返回数据, gin 能返回的数据有很多, 但实际使用中应该是返回 json 的情况最多, 所以下面只介绍返回 json 的情况 (其他数据类型应该也大差不差)

  1. 可以将结构体直接作为 json 返回

    type josnTest struct {
        Name string `json:"username"`
        Age  int   `json:"age"`
    }
    
    func main() {
        r := gin.Default()
    
        r.GET("/json", func(ctx *gin.Context) {
        	test := josnTest{"测试", 1}
        	ctx.JSON(http.StatusOK, test)
        })
    
        r.Run(":8080")
    }
    
  2. 可以使用 gin.H()返回

    type josnTest struct {
        Name string `json:"username"`
        Age  int   `json:"age"`
    }
    
    func main() {
        r := gin.Default()
    
        r.GET("/json", func(ctx *gin.Context) {
    	    ctx.JSON(http.StatusOK, gin.H{"username": "测试", "age": 1})
        })
    
        r.Run(":8080")
    }
    

获取参数

获取 QueryString 中的参数

queryString 是指 url 中 ? 后面类似于键值对的参数

请求路径为: http://localhost:8090/query?username=zhangsan&age=17

r.GET("/query", func(ctx *gin.Context) {
    // Query 是从直接获取数据
	username := ctx.Query("username")
    // DefaultQuery 是如果没有获取到数据则会赋一个默认值
    // 注意获取到的数据均为 string, 需要转换类型
	age, _ := strconv.Atoi(ctx.DefaultQuery("age", "12"))
	test := josnTest{username, age}
	ctx.JSON(http.StatusOK, test)
})

获取表单中的数据

请求方式为 post, 请求数据通过 form 表单提交

r.POST("/form", func(ctx *gin.Context) {
    // 与上一段代码类似
	username := ctx.DefaultPostForm("username", "lisi")
	age, _ := strconv.Atoi(ctx.PostForm("age"))
	ctx.JSON(http.StatusOK, gin.H{"username": username, "age": age})
})

获取 json 数据

请求方式为 post, 请求数据通过 json 提交

r.POST("/json", func(ctx *gin.Context) {
    // 先获取到原始的 json 数据
	res, _ := ctx.GetRawData()
	var t josnTest
    // 再进行反序列化
	json.Unmarshal(res, &t)
	fmt.Println(t)
	ctx.JSON(http.StatusOK, t)
})

获取路径中的数据

REST 风格 中, 我们常常会直接使用/[参数]这种形式来发送请求

请求路径: http://localhost:8090/zhangsan/13

// 注意接口的写法
r.GET("/:username/:age", func(ctx *gin.Context) {
    // 第一种方式: 获取对应名称的参数
	username := ctx.Param("username")
	age := ctx.Param("age")

    // 第二种方式: 获取全部参数后再逐个获取
	ps := ctx.Params
	temp1 := ps.ByName("username")
	temp2, _ := ps.Get("age")
	fmt.Println(temp1, "   ", temp2)

	ctx.JSON(http.StatusOK, gin.H{"username": username, "age": age})
})

参数绑定

gin 提供了强大的参数绑定功能, 我们可以使用 .ShouldBind() 自动提取数据并绑定到对应的结构体上

结构体的定义为:

type bindingTest struct {
	User string `form:"username" json:"username" binding:"required"`
	Pwd  string `form:"password" json:"password" binding:"required"`
}

绑定 QueryString 参数

r.GET("/binding/get", func(ctx *gin.Context) {
	var test bindingTest
	err := ctx.ShouldBind(&test)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	} else {
		fmt.Println(test.User, "   ", test.Pwd)
		ctx.JSON(http.StatusOK, test)
	}
})

绑定 form 参数

r.POST("/binding/post/form", func(ctx *gin.Context) {
	var test bindingTest
	err := ctx.ShouldBind(&test)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	} else {
		fmt.Println(test.User, "   ", test.Pwd)
		ctx.JSON(http.StatusOK, test)
	}
})

绑定 json 参数

r.POST("/binding/post/json", func(ctx *gin.Context) {
	var test bindingTest
	err := ctx.ShouldBind(&test)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	} else {
		fmt.Println(test.User, "   ", test.Pwd)
		ctx.JSON(http.StatusOK, test)
	}
})

路由组

在实际应用中我们的接口很多情况下会有相同的前缀(如上一部分代码中每个路径前都有 /binding), 所以 gin 中有一个路由组的概念, 通过设置路由组从而减少重复代码的书写

路由组的基本设置为:

r := gin.Default()
group := r.Group("/group")
// 这个括号可以不加
{
    // 路径为 /group/test
    group.GET("/test", )
}

重定向

http 重定向

注意: http 重定向时, 路径要加上 http:// 前缀

r.GET("/get/baidu", func(ctx *gin.Context) {
	ctx.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")

    // 这样是重定向至 /get/www.baidu.com
	// ctx.Redirect(http.StatusMovedPermanently, "www.baidu.com")
})

路由重定向

r.GET("/get/ping", func(ctx *gin.Context) {
    // 重定向至 /ping
	ctx.Request.URL.Path = "/ping"
	r.HandleContext(ctx)
})

r.GET("/ping", func(ctx *gin.Context) {
	ctx.String(http.StatusOK, "pong")
})

程序清单

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

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

type josnTest struct {
	Name string `json:"username"`
	Age  int    `json:"age"`
}

type bindingTest struct {
	User string `form:"username" json:"username" binding:"required"`
	Pwd  string `form:"password" json:"password" binding:"required"`
}

func ginFunc() *gin.Engine {
	r := gin.Default()

	r.GET("/ping", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "pong")
	})

	r.GET("/json", func(ctx *gin.Context) {
		test := josnTest{"测试", 1}
		fmt.Println(test)
		ctx.JSON(http.StatusOK, test)
	})

	r.GET("/query", func(ctx *gin.Context) {
		username := ctx.Query("username")
		age, _ := strconv.Atoi(ctx.DefaultQuery("age", "12"))
		test := josnTest{username, age}
		ctx.JSON(http.StatusOK, test)
	})

	r.POST("/form", func(ctx *gin.Context) {
		username := ctx.DefaultPostForm("username", "lisi")
		age, _ := strconv.Atoi(ctx.PostForm("age"))
		fmt.Print(age)
		ctx.JSON(http.StatusOK, gin.H{"username": username, "age": age})
	})

	r.POST("/json", func(ctx *gin.Context) {
		res, _ := ctx.GetRawData()
		var t josnTest
		json.Unmarshal(res, &t)
		fmt.Println(t)
		ctx.JSON(http.StatusOK, t)
	})

	r.GET("/:username/:age", func(ctx *gin.Context) {
		username := ctx.Param("username")
		age := ctx.Param("age")
		// ps := ctx.Params
		// temp1 := ps.ByName("username")
		// temp2, _ := ps.Get("age")
		// fmt.Println(temp1, "   ", temp2)
		ctx.JSON(http.StatusOK, gin.H{"username": username, "age": age})
	})

	return r
}

func ginBindingFunc() *gin.Engine {
	r := gin.Default()
	binding := r.Group("/binding")
	{
		binding.GET("/get", func(ctx *gin.Context) {
			var test bindingTest
			err := ctx.ShouldBind(&test)
			if err != nil {
				ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			} else {
				fmt.Println(test.User, "   ", test.Pwd)
				ctx.JSON(http.StatusOK, test)
			}
		})

		binding.POST("/post/form", func(ctx *gin.Context) {
			var test bindingTest
			err := ctx.ShouldBind(&test)
			if err != nil {
				ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			} else {
				fmt.Println(test.User, "   ", test.Pwd)
				ctx.JSON(http.StatusOK, test)
			}
		})

		binding.POST("/post/json", func(ctx *gin.Context) {
			var test bindingTest
			err := ctx.ShouldBind(&test)
			if err != nil {
				ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			} else {
				fmt.Println(test.User, "   ", test.Pwd)
				ctx.JSON(http.StatusOK, test)
			}
		})
	}

	return r
}

func ginRedirect() *gin.Engine {
	r := gin.Default()

	r.GET("/get/baidu", func(ctx *gin.Context) {
		ctx.Redirect(http.StatusMovedPermanently, "www.baidu.com")
	})

	r.GET("/get/ping", func(ctx *gin.Context) {
		ctx.Request.URL.Path = "/ping"
		r.HandleContext(ctx)
	})

	r.GET("/ping", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "pong")
	})

	return r
}

func main() {
	r := ginFunc()
	// r := ginBindingFunc()
	// r := ginRedirect()
	r.Run(":8090")
}

参考文章:

Gin 框架官方文档

Gin 框架的介绍及快速使用