安装
go get github.com/gin-gonic/gin
HelloWorld
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由
router := gin.Default()
// 设置路由
router.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World!")
})
// 启动方式一
// router.Run(":8080")
// 启动方式二
http.ListenAndServe(":8080", router)
// 注意:可以指定IP地址,router.Run("0.0.0.0:8000")
}
响应
字符串
router.GET("/txt", func(c *gin.Context) {
c.String(http.StatusOK, "这是一个字符串")
})
Json
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "success", "data": "hello world"})
})
router.GET("/", func(c *gin.Context) {
type Resp struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
}
resp := Resp{"Tom", 25, "Beijing"}
c.JSON(http.StatusOK, resp)
})
文件
// 创建路由
router := gin.Default()
// 方法一:指定静态文件目录
// 第一个参数:网页请求这个静态目录的前缀, 第二个参数:指定静态文件目录
// 注意,前缀不要重复
// router.StaticFS("/static", http.Dir("static"))
// 方法二:指定静态文件
router.StaticFile("/img1.png", "static/img1.png")
router.Run(":8080")
重定向
// 创建路由
router := gin.Default()
// 301重定向
// router.GET("/old_path", func(c *gin.Context) {
// c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
// })
// 302重定向
router.GET("/old_path", func(c *gin.Context) {
c.Redirect(http.StatusFound, "https://www.baidu.com")
})
router.Run(":8080")
设置响应头
func main() {
// 创建路由
router := gin.Default()
// 设置响应头
router.GET("/respnse/header", func(c *gin.Context) {
c.Header("lemon", "440886666")
c.JSON(200, gin.H{
"code": http.StatusOK,
"message": "请看看响应头",
})
})
router.Run(":8080")
}
请求
请求参数获取
Query 查询参数
func queryGet(c *gin.Context) {
// 获取查询参数
// 返回: 参数值
fmt.Println(c.Query("name"))
// 返回: 参数值;是否存在(bool)
fmt.Println(c.GetQuery("name"))
// 返回: 参数值数组(上面的方法只会返回第一个参数值(如果有多个))
fmt.Println(c.QueryArray("name"))
// 设置默认值
fmt.Println(c.DefaultQuery("age", "18"))
}
func main() {
// 创建路由
router := gin.Default()
// 获取查询参数
router.GET("/query", queryGet)
router.Run(":8080")
}
Parms 路径参数(动态参数)
func paramGet(c *gin.Context) {
// 获取动态路径参数
fmt.Println(c.Param("user_id"))
fmt.Println(c.Param("username"))
}
func main() {
// 创建路由
router := gin.Default()
// 获取查询参数
router.GET("/param/:user_id/:username", paramGet)
router.Run(":8080")
}
获取表单数据
func formdataGet(c *gin.Context) {
fmt.Println(c.PostForm("name"))
// 获取多个值(数组)
fmt.Println(c.PostFormArray("hobby"))
// 设置默认值
fmt.Println(c.DefaultPostForm("email", "123@163.com"))
// 获取整个表单
form, err := c.MultipartForm()
fmt.Println(form, err)
// 通过 form 获取参数值
fmt.Println(form.Value["hobby"])
}
func main() {
// 创建路由
router := gin.Default()
// 获取查询参数
router.POST("/formdata", formdataGet)
router.Run(":8080")
}
获取请求体数据(原生)
func rowBodyGet(c *gin.Context) {
data, err := c.GetRawData()
fmt.Println(string(data), err)
}
func main() {
// 创建路由
router := gin.Default()
// 获取查询参数
router.POST("/raw", rowBodyGet)
router.Run(":8080")
}
获取请求头
func main() {
// 创建路由
router := gin.Default()
// 获取查询参数
router.POST("/header", func(c *gin.Context) {
// 不区分大小写
// 获取请求头
fmt.Println(c.GetHeader("User-Agent"))
// 获取 header, 通过 header 获取头信息
header := c.Request.Header
fmt.Println(header)
fmt.Println(header.Get("User-Agent"))
fmt.Println(header["User-Agent"])
// 区分大小写
fmt.Println(header["Token"])
// 不区分大小写
fmt.Println(header.Get("Token"))
c.JSON(200, gin.H{
"msg": "success",
})
})
router.Run(":8080")
}
请求参数绑定与校验
Json Bind
type User struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
Hobbies []string `json:"hobbies"`
}
func main() {
router := gin.Default()
router.POST("/bind/json", func(c *gin.Context) {
var user User
err := c.ShouldBindJSON(&user)
if err != nil {
c.JSON(200, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"user": user})
})
router.Run(":8080")
}
Query Bind
type User1 struct {
Name string `json:"name" form:"name"`
Age int `json:"age" form:"age"`
City string `json:"city" form:"city"`
Hobbies []string `json:"hobbies" form:"hobbies"`
}
func main() {
router := gin.Default()
router.POST("/bind/query", func(c *gin.Context) {
var user User1
err := c.ShouldBindQuery(&user)
if err != nil {
c.JSON(200, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"user": user})
})
router.Run(":8080")
}
Uri Bind
type User2 struct {
Name string `json:"name" form:"name" uri:"name"`
Age int `json:"age" form:"age" uri:"age"`
City string `json:"city" form:"city" uri:"city"`
}
func main() {
router := gin.Default()
router.POST("/bind/param/:name/:age/:city", func(c *gin.Context) {
var user User2
err := c.ShouldBindUri(&user)
if err != nil {
c.JSON(200, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"user": user})
})
router.Run(":8080")
}
Auto Bind
可以使用 ShouldBind 进行绑定,该方法会自动根据 content-type 类型来选择对应的绑定方式进行绑定,包括 form-data、 x-www-form-urlencode。
type User4 struct {
Name string `json:"name" form:"name" uri:"name"`
Age int `json:"age" form:"age" uri:"age"`
City string `json:"city" form:"city" uri:"city"`
Hobbies []string `json:"hobbies" form:"hobbies"`
}
func main() {
router := gin.Default()
router.POST("/bind/auto", func(c *gin.Context) {
var user User4
err := c.ShouldBind(&user)
if err != nil {
c.JSON(200, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"user": user})
})
router.Run(":8080")
}
参数校验
参数校验使用 binding 标签来设置,多个校验规则之间使用 逗号 分隔。
demo
type User5 struct {
Name string `json:"name" form:"name" binding:"required,min=2,max=10"`
Age int `json:"age" form:"age"`
City string `json:"city" form:"city"`
Hobbies []string `json:"hobbies" form:"hobbies"`
}
func main() {
router := gin.Default()
router.POST("/param/valid", func(c *gin.Context) {
var user User5
err := c.ShouldBindQuery(&user)
if err != nil {
c.JSON(200, gin.H{"error": err})
return
}
c.JSON(200, gin.H{"user": user})
})
router.Run(":8080")
}
常见校验器
// 不能为空,并且不能没有这个字段
required: 必填字段,如:binding:"required"
// 针对字符串的长度
min 最小长度,如:binding:"min=5"
max 最大长度,如:binding:"max=10"
len 长度,如:binding:"len=6"
// 针对数字的大小
eq 等于,如:binding:"eq=3"
ne 不等于,如:binding:"ne=12"
gt 大于,如:binding:"gt=10"
gte 大于等于,如:binding:"gte=10"
lt 小于,如:binding:"lt=10"
lte 小于等于,如:binding:"lte=10"
// 针对同级字段的
eqfield 等于其他字段的值,如:PassWord string `binding:"eqfield=Password"`
nefield 不等于其他字段的值
- 忽略字段,如:binding:"-"
gin 内置校验器
// 枚举 只能是red 或green
oneof=red green
// 字符串
contains=fengfeng // 包含fengfeng的字符串
excludes // 不包含
startswith // 字符串前缀
endswith // 字符串后缀
// 数组
dive // dive后面的验证就是针对数组中的每一个元素
// 网络验证
ip
ipv4
ipv6
uri
url
// uri 在于I(Identifier)是统一资源标示符,可以唯一标识一个资源。
// url 在于Locater,是统一资源定位符,提供找到该资源的确切路径
// 日期验证 1月2号下午3点4分5秒在2006年
datetime=2006-01-02
自定义错误提示信息
当校验失败,默认的错误消息不是很友好,可以自定义错误信息标签,具体的做法这里就不展开介绍了。
自定义校验器
如果现成的校验器无法满足使用,可以根据需求自定义校验器。
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("sign", signValid)
}
文件操作
上传文件
上传单个文件
func main() {
router := gin.Default()
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload/single", func(c *gin.Context) {
file, _ := c.FormFile("file")
log.Println(file.Filename)
// 文件存储目标路径
dst := "./static/" + file.Filename
// 上传文件
// 这里保存文件的方法有多种,例如也可以先创建好目标文件,再使用 io.Copy(out, fileRead) 拷贝文件内容
c.SaveUploadedFile(file, dst)
c.JSON(http.StatusOK, gin.H{"message": "upload success"})
})
router.Run(":8080")
}
上传多个文件
func main() {
router := gin.Default()
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload/multi", func(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["upload"]
for _, file := range files {
log.Printf(file.Filename)
// 文件存储目标路径
dst := "./static/" + file.Filename
// 上传文件
// 这里保存文件的方法有多种,例如也可以先创建好目标文件,再使用 io.Copy(out, fileRead) 拷贝文件内容
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{"message": "upload success"})
})
router.Run(":8080")
}
下载文件
func main() {
router := gin.Default()
router.GET("/download", func(c *gin.Context) {
value := c.Query("filename")
log.Println(value)
dst := "./static/" + value
// 设置响应头
// 表明是文件流
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename="+value)
c.Header("Content-Transfer-Encoding", "binary")
c.File(dst)
})
router.Run(":8080")
}
中间件(钩子函数)与路由分组
注册中间件
func handler(c *gin.Context) {
fmt.Println("Hello, World!")
c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
}
// 定义钩子函数
func m1(c *gin.Context) {
fmt.Println("钩子函数m1执行...")
}
// 定义钩子函数
func m2(c *gin.Context) {
fmt.Println("钩子函数m2执行...")
}
func main() {
router := gin.Default()
router.GET("/hook", m1, handler, m2)
router.Run(":8080")
}
执行结果:
钩子函数m1执行...
Hello, World!
钩子函数m2执行...
拦截( Abort )与放行( Next )
调用 Abort 后,不会继续执行当前中间后面的中间件,不过会继续执行完 Abort 后面的语句(如果有); 调用 Next 后,就会执行下一个中间件,直到后面所有中间件完成了执行返回时再继续执行 Next 后面的语句。
func handler(c *gin.Context) {
fmt.Println("Hello, World!...in")
c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
// 拦截
c.Abort()
fmt.Println("Hello, World!...out")
}
// 定义钩子函数 m1
func m1(c *gin.Context) {
fmt.Println("钩子函数m1执行...in")
// 放行:调用下一个钩子函数
c.Next()
fmt.Println("钩子函数m1执行...out")
}
// 定义钩子函数 m2
func m2(c *gin.Context) {
fmt.Println("钩子函数m2执行...in")
// 放行:调用下一个钩子函数
c.Next()
fmt.Println("钩子函数m2执行...out")
}
// 定义钩子函数 m3
func m3(c *gin.Context) {
fmt.Println("钩子函数m3执行...in")
// 放行:调用下一个钩子函数
c.Next()
fmt.Println("钩子函数m3执行...out")
}
func main() {
router := gin.Default()
router.GET("/hook", m1, m2, handler, m3)
router.Run(":8080")
}
执行结果:
钩子函数m1执行...in
钩子函数m2执行...in
Hello, World!...in
Hello, World!...out
钩子函数m2执行...out
钩子函数m1执行...out
全局注册中间件
除了上面给出的局部中间件,还可以注册全局中间件,如下:
func handler01(c *gin.Context) {
fmt.Println("Hello, World!")
c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
}
// 定义钩子函数
func m01(c *gin.Context) {
fmt.Println("钩子函数m1执行...")
}
// 定义钩子函数
func m02(c *gin.Context) {
fmt.Println("钩子函数m2执行...")
}
func main() {
router := gin.Default()
// 注册全局中间件
router.Use(m01, m02)
router.GET("/hook/global", handler01)
router.Run(":8080")
}
中间件数据传递
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func m10(c *gin.Context) {
fmt.Println("m10_in")
var user User
c.ShouldBindJSON(&user)
// 设置一个上下文变量,供后续中间件使用
c.Set("user", user)
c.Next()
fmt.Println("m10_out")
}
func main() {
router := gin.Default()
router.Use(m10)
router.POST("/hook_data_transfer", func(context *gin.Context) {
// 从上下文变量中获取 user 数据
value, exists := context.Get("user")
// 判断是否存在
if exists {
user, _ := value.(User)
fmt.Println("获取到了 m10 传递过来的 user 数据:", user)
context.JSON(200, gin.H{"message": "success"})
}
})
router.Run(":8080")
}
路由分组
func middle(c *gin.Context) {
fmt.Println("This is a middleware")
}
func main() {
rouer := gin.Default()
// 将中间件应用到 /api 组下
r := rouer.Group("/api").Use(middle)
// 基于 /api 组下的路由
r.GET("/users", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "This is a users API",
})
})
r.GET("/posts", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "This is a posts API",
})
})
rouer.Run(":8080")
}
参考:
docs.fengfengzhidao.com/#/README
(https://www.kancloud.cn/shuangdeyu/gin_book