Gin学习笔记2.0 | 青训营

148 阅读15分钟

Gin介绍

Gin 是一个 Go (Golang) 编写的轻量级 http web 框架,运行速度非常快,擅长处理高并发。

Gin 的官网:Gin官网

Gin Github 地址:Gin-Github

博主 Demo 地址:Demo

Gin安装

要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区(GoPath)。

  1. 下载并安装 gin:

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

  2. 将 gin 引入到代码中:

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

  3. (可选)如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包:1import "net/http" 1

  4. 新建 Main.go 配置路由

package main
import (
    "github.com/gin-gonic/gin"
)
func main() {
    // 创建一个默认的路由引擎
    r := gin.Default()
    // 配置路由
    r.GET("/", func(c *gin.Context) {
  	  	c.JSON(200, gin.H{ // c.JSON:返回 JSON 格式的数据
    		"message": "Hello world!", 
        })
	})
    // 启动 HTTP 服务,默认在 0.0.0.0:8080 启动服务
    r.Run()
}
  1. 运行你的项目

    go run main.go

  2. 如果需要改变你的默认端口

    r.Run(":9000")

  • 因为伟大的长城,所以国内用户有时会出现get不成功的情况,解决如下

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

Routing 路由

概述

路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何响应客户端对某个网站节点的访问。

RESTful API 是目前比较成熟的一套互联网应用程序的 API 设计理论,所以我们设计我们的路由的时候建议参考 RESTful API 指南。

请求方式操作
GET (SELECT)从服务器取出资源(一项或多项)
POST (CREATE)在服务器新建一个资源
PUT (UPDATE)在服务器更新资源(客户端提供改变后的完整资源)
DELETE (DELETE)从服务器删除资源

c.XXX()

c指的是 *gin.Context

  1. c.String()

返回一个字符串

r.GET("/news", func(c *gin.Context) {
	aid := c.Query("aid")
	c.String(200, "aid=%s", aid)
})
  1. c.JSON()

返回一个JSON数据


func main() {
	r := gin.Default()
	// gin.H 是 map[string]interface{}的缩写
	r.GET("/someJSON", func(c *gin.Context) {
		// 方式一:自己拼接 JSON
		c.JSON(http.StatusOK, gin.H{"message": "Hello world!"})
	})
	r.GET("/moreJSON", func(c *gin.Context) {
		// 方法二:使用结构体
		var msg struct {
			Name string `json:"user"` Message string
			Age int
		}
		msg.Name = "zhangsan"
		msg.Message = "Hello world!"
		msg.Age = 18
		c.JSON(http.StatusOK, msg)
	})
	r.Run(":8080")
}
  1. c.JSONP()

JSONP(JSON with Padding)是一种用于解决跨域请求的简单技术手段。在传统的Web应用中,由于浏览器的同源策略(Same-Origin Policy),不同域名之间的JavaScript脚本是无法直接进行跨域请求的。而JSONP是一种绕过这种限制的方法,它允许在不同域之间通过动态创建<script>标签来获取数据。

func main() {
	r := gin.Default()
	r.GET("/JSONP", func(c *gin.Context) {
		data := map[string]interface{}{"foo": "bar"}
		// /JSONP?callback=x
		// 将输出:x({\"foo\":\"bar\"})
		c.JSONP(http.StatusOK, data)
	})
	// 监听并在 0.0.0.0:8080 上启动服务
	r.Run(":8080")
}
  1. c.XMl()
func main() {
	r := gin.Default()
	// gin.H 是 map[string]interface{}的缩写
	r.GET("/someXML", func(c *gin.Context) {
		// 方式一:自己拼接 JSON
		c.XML(http.StatusOK, gin.H{"message": "Hello world!"})
	})
	r.GET("/moreXML", func(c *gin.Context) {
		// 方法二:使用结构体
		type MessageRecord struct {
			Name    string
			Message string
			Age     int
		}
		var msg MessageRecord
		msg.Name = "zhangsan"
		msg.Message = "Hello world!"
		msg.Age = 18
		c.XML(http.StatusOK, msg)
	})
	r.Run(":8080")
}
  1. c.HTML()

简单演示:

router.GET("/", func(c *gin.Context) {
	c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{ 
        "title": "前台首页"
	})
})

Get,Post传值

Get请求传值

GET /user?uid=20&page=1

router.GET("/user", func(c *gin.Context) {
    uid := c.Query("uid")
    page := c.DefaultQuery("page", "0")
    c.String(200, "uid=%v page=%v", uid, page)
})

动态路由传值

域名 /user/20

r.GET("/user/:uid", func(c *gin.Context) {
    uid := c.Param("uid")
    c.String(200, "userID=%s", uid)
})

获取 form 表单数据

  • 直接获取
router.POST("/doAddUser", func(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")
    age := c.DefaultPostForm("age", "20")
    
	c.JSON(200, gin.H{ 
        "usernmae": username, 
        "password": password, 
        "age": age, 
    })
})
  • 绑定到结构体

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中 QueryString、form 表单、JSON、XML 等参数到结构体中。 下面的示例代码演示了==.ShouldBind()==强大的功能,它能够基于请求自动提取 JSON、form 表单和 QueryString 类型的数据,并把值绑定到指定的结构体对象。

//结构体
//注意首字母大写
type Userinfo struct {
	Username string `form:"username" json:"user"` 
    Password string `form:"password" json:"password"` 
}

Get 传值绑定到结构体

/?username=zhangsan&password=123456

router.GET("/", func(c *gin.Context) {
    var userinfo Userinfo
    if err := c.ShouldBind(&userinfo); err == nil {
    	c.JSON(http.StatusOK, userinfo)
    } else {
    	c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
})

Post 传值绑定到结构体

router.POST("/doLogin", func(c *gin.Context) {
	var userinfo Userinfo
	if err := c.ShouldBind(&userinfo); err == nil {
		c.JSON(http.StatusOK, userinfo)
	} else {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	}
})

路由组

简单路由

参考

func main() {
	router := gin.Default()
	// 简单的路由组: v1
	v1 := router.Group("/v1")
	{
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint)
		v1.POST("/read", readEndpoint)
	}
	// 简单的路由组: v2
	v2 := router.Group("/v2")
	{
		v2.POST("/login", loginEndpoint)
		v2.POST("/submit", submitEndpoint)
		v2.POST("/read", readEndpoint)
	}
	router.Run(":8080")
}

多路由

  1. 新建 routes 文件夹,routes 文件下面新建 adminRoutes.go、apiRoutes.go、defaultRoutes.go
func AdminRoutesInit(router *gin.Engine) {
	adminRouter := router.Group("/admin")
	{
		adminRouter.GET("/user", func(c *gin.Context) {
			c.String(http.StatusOK, "用户")
		})
		adminRouter.GET("/news", func(c *gin.Context) {
			c.String(http.StatusOK, "news")
		})
	}
}
func ApiRoutesInit(router *gin.Engine) {
	apiRoute := router.Group("/api")
	{
		apiRoute.GET("/user", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"username": "张三", "age": 20})
		})
		apiRoute.GET("/news", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"title": "这是新闻"})
		})
	}
}
func DefaultRoutesInit(router *gin.Engine) {
	defaultRoute := router.Group("/")
	{
		defaultRoute.GET("/", func(c *gin.Context) {
			c.String(200, "首页")
		})
	}
}
  1. 配置main.go
func main() {
    r := gin.Default()
    routes.AdminRoutesInit(r)
    routes.ApiRoutesInit(r)
    routes.DefaultRoutesInit(r)
    r.Run(":8080")
}

HTML模板渲染

模板渲染

Gin 框架中使用 c.HTML 可以渲染模板,渲染模板前需要使用LoadHTMLGlob()或者LoadHTMLFiles()方法加载模板。

单目录

首先在项目根目录新建 templates 文件夹,然后在文件夹中新建index.html

func main() {
	router := gin.Default()
	//加载模板
	router.LoadHTMLGlob("templates/*")
	//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
	
	router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", gin.H{ "title": "Main website", })
	})
	router.Run(":8080")
}

多目录

Gin 框架中如果不同目录下面有同名模板,我们需要使用{{define }}

  1. 创建templates/admin/index.html
<!-- 相当于给模板定义一个名字 define end 成对出现-->
{{define "admin/index.html" }}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
    <h1>这是一个管理员登录模板</h1>
    <h3>{{.title}}</h3>
    {{/*这里使用了全局模板函数*/}}
    <h3>{{.now | unixToDate}}</h3>
    </body>
    </html>
{{end}}
  1. 创建templates/default/index.html
{{define "default/index.html" }}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
    <h1>这是一个用户登录模板</h1>
    <h3>{{.title}}</h3>
    </body>
    </html>
{{end}}
  1. main中代码
func main() {
	router := gin.Default()
    //记得改变原先的路径
	router.LoadHTMLGlob("templates/**/*")
	router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "default/index.html", gin.H{"title": "前台首页"})
	})
	router.GET("/admin", func(c *gin.Context) {
		c.HTML(http.StatusOK, "admin/index.html", gin.H{"title": "后台首页"})
	})
	router.Run(":8080")
}

语法

  1. 输出数据

    {{.XX}}

router.GET("/", func(c *gin.Context) {
	c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{ 
        "title": "前台首页", 
        //这里是一个结构体,此处省略
        "user": user, 
    })
})
{{ define "default/index.html" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>前台模板</h1>
    <h3>{{.title}}</h3>
    <h4>{{.user.Name}}</h4>
    <h4>{{.user.Age}}</h4>
</body>
</html>
{{end}}
  1. 注释

{{/* a comment */}}

  1. 变量
h4>{{$obj := .title}}</h4>
<h4>{{$obj}}</h4>

4.移除空格

有时候我们在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲染出来的内容可能就和我们想的不一样,这个时候可以使用{{-语法去除模板内容左侧的所有空白符号, 使用-}}去除模板内容右侧的所有空白符号。

{{- .Name -}}

5.比较函数

布尔函数会将任何类型的零值视为假,其余视为真。

下面是定义为函数的二元比较运算的集合:

  • eq 如果 arg1 == arg2 则返回真
  • ne 如果 arg1 != arg2 则返回真
  • lt 如果 arg1 < arg2 则返回真
  • le 如果 arg1 <= arg2 则返回真
  • gt 如果 arg1 > arg2 则返回真
  • ge 如果 arg1 >= arg2 则返回真

其它此处省略,更多详情点击官方中文文档了解

静态文件配置

当我们渲染的 HTML 文件中引用了静态文件时,我们需要配置静态 web 服务r.Static("/static", "./static") 前面的/static 表示路由 后面的./static 表示路径

func main() {
    r := gin.Default()
    r.Static("/static", "./static")
    r.LoadHTMLGlob("templates/**/*")
    // ... 
    r.Run(":8080")
}
<link rel="stylesheet" href="/static/css/base.css" />

Controller 控制器

分组

  1. 新建Controller

    • 新建 controller/admin/NewsController.go

      package admin
      import ( 
          "net/http"
      	"github.com/gin-gonic/gin"
      )
      type NewsController struct {
          
      }
      
      func (c NewsController) Index(ctx *gin.Context) {
      	ctx.String(http.StatusOK, "新闻首页")
      }
      
    • 新建 controller/admin/UserController.go

      package admin
      import ( 
          "net/http"
      	"github.com/gin-gonic/gin"
      )
      type UserController struct {
      }
      
      func (c UserController) Index(ctx *gin.Context) {
      	ctx.String(http.StatusOK, "这是用户首页")
      }
      func (c UserController) Add(ctx *gin.Context) {
      	ctx.String(http.StatusOK, "增加用户")
      }
      
  2. 配置对应的路由 --adminRoutes.go

    func AdminRoutesInit(router *gin.Engine) {
    	adminRouter := router.Group("/admin"){
    		adminRouter.GET("/user", admin.UserController{}.Index)
    		adminRouter.GET("/user/add", admin.UserController{}.Add)
    		adminRouter.GET("/news", admin.NewsController{}.Add)
    	}
    }
    

继承

  1. 新建controller/admin/BaseController.go
package admin
import ( "net/http"
"github.com/gin-gonic/gin"
)
type BaseController struct {
}

func (c BaseController) Success(ctx *gin.Context) {
	ctx.String(http.StatusOK, "成功")
}
func (c BaseController) Error(ctx *gin.Context) {
	ctx.String(http.StatusOK, "失败")
}
  1. NewsController 继承 BaseController
package admin
import ( "github.com/gin-gonic/gin"
)
type NewsController struct {
	BaseController
}
func (c NewsController) Index(ctx *gin.Context) {
	c.Success(ctx)
}

Gin中间件

介绍

Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作

路由中间件

快速入门

Gin 中的中间件必须是一个 gin.HandlerFunc 类型,配置路由的时候可以传递多个func 回调函数,最后一个 func 回调函数前面触发的方法都可以称为中间件。

func initMiddleware(ctx *gin.Context) {
	fmt.Println("我是一个中间件")
}
func main() {
	r := gin.Default()
	r.GET("/", initMiddleware, func(ctx *gin.Context) {
		ctx.String(200, "首页--中间件演示")
	})
	r.GET("/news", initMiddleware, func(ctx *gin.Context) {
		ctx.String(200, "新闻页面--中间件演示")
	})
	r.Run(":8080")
}

c.Next()

中间件里面加上 ctx.Next()可以让我们在路由匹配完成后执行一些操作。比如我们统计一个请求的执行时间。

关于Next()的另一篇博客

package main
import ( "fmt"
"time"
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) {
	fmt.Println("1-执行中中间件")
	start := time.Now().UnixNano()
	// 调用该请求的剩余处理程序
	ctx.Next()
	fmt.Println("3-程序执行完成 计算时间")
	// 计算耗时 Go 语言中的 Since()函数保留时间值,并用于评估与实际时间的差异
	end := time.Now().UnixNano()
	fmt.Println(end - start)
}
func main() {
	r := gin.Default()
	r.GET("/", initMiddleware, func(ctx *gin.Context) {
		fmt.Println("2-执行首页返回数据")
		ctx.String(200, "首页--中间件演示")
	})
	r.GET("/news", initMiddleware, func(ctx *gin.Context) {
		ctx.String(200, "新闻页面--中间件演示")
	})
	r.Run(":8080")
}

执行顺序

按照添加顺序执行

func initMiddlewareOne(ctx *gin.Context) {
	fmt.Println("initMiddlewareOne--1-执行中中间件")
	// 调用该请求的剩余处理程序
	ctx.Next()
	fmt.Println("initMiddlewareOne--2-执行中中间件")
}
func initMiddlewareTwo(ctx *gin.Context) {
	fmt.Println("initMiddlewareTwo--1-执行中中间件")
	// 调用该请求的剩余处理程序
	ctx.Next()
	fmt.Println("initMiddlewareTwo--2-执行中中间件")
}
func main() {
	r := gin.Default()
	r.GET("/", initMiddlewareOne, initMiddlewareTwo, func(ctx *gin.Context) {
		fmt.Println("执行路由里面的程序")
		ctx.String(200, "首页--中间件演示")
	})
	r.Run(":8080")
}

输出如下

initMiddlewareOne--1-执行中中间件 initMiddlewareTwo--1-执行中中间件 执行路由里面的程序 initMiddlewareTwo--2-执行中中间件 initMiddlewareOne--2-执行中中间件

c.Abort()

Abort 是终止的意思, c.Abort() 表示终止调用该请求的剩余处理程序

package main
import ( "fmt"
"github.com/gin-gonic/gin"
)
func initMiddlewareOne(ctx *gin.Context) {
	fmt.Println("initMiddlewareOne--1-执行中中间件")
	// 调用该请求的剩余处理程序
	ctx.Next()
	fmt.Println("initMiddlewareOne--2-执行中中间件")
}
func initMiddlewareTwo(ctx *gin.Context) {
	fmt.Println("initMiddlewareTwo--1-执行中中间件")
	// 终止调用该请求的剩余处理程序
	ctx.Abort()
	fmt.Println("initMiddlewareTwo--2-执行中中间件")
}
func main() {
	r := gin.Default()
	r.GET("/", initMiddlewareOne, initMiddlewareTwo, func(ctx *gin.Context) {
		fmt.Println("执行路由里面的程序")
		ctx.String(200, "首页--中间件演示")
	})
	r.Run(":8080")
}

输出如下

initMiddlewareOne--1-执行中中间件 initMiddlewareTwo--1-执行中中间件 initMiddlewareTwo--2-执行中中间件 initMiddlewareOne--2-执行中中间件

路由分组中间件

  1. 为路由组注册中间件(两种写法)

    • shopGroup := r.Group("/shop", StatCost())
      {
      	shopGroup.GET("/index", func(c *gin.Context) {...})
      ... }
      
    • shopGroup := r.Group("/shop")
      shopGroup.Use(StatCost())
      {
      	shopGroup.GET("/index", func(c *gin.Context) {...})
      ... }
      
  2. 分组路由 AdminRoutes.go 中配置中间件

    package main
    
    import (
    	"Gin-Demo/controller/admin"
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    func initMiddleware(ctx *gin.Context) {
    	fmt.Println("路由分组中间件")
    	// 调用该请求的剩余处理程序
    	ctx.Next()
    }
    func AdminRoutesInit(router *gin.Engine) {
    	adminRouter := router.Group("/admin", initMiddleware)
    	{
    		adminRouter.GET("/user", admin.UserController{}.Index)
    		adminRouter.GET("/user/add", admin.UserController{}.Add)
    		adminRouter.GET("/news", func(c *gin.Context) {
    			c.String(http.StatusOK, "news")
    		})
    	}
    }
    

全局中间件

package main
import ( "fmt"
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) {
	fmt.Println("全局中间件 通过 r.Use 配置")
	// 调用该请求的剩余处理程序
	ctx.Next()
}
func main() {
	r := gin.Default()
	r.Use(initMiddleware)
	r.GET("/", func(ctx *gin.Context) {
		ctx.String(200, "首页--中间件演示")
	})
	r.GET("/news", func(ctx *gin.Context) {
		ctx.String(200, "新闻页面--中间件演示")
	})
	r.Run(":8080")
}

数据共享

  • 设置值

    ctx.Set("username", "张三")

  • 获取值

    username, _ := ctx.Get("username")

  1. 中间件设置值
func InitAdminMiddleware(ctx *gin.Context) {
	fmt.Println("路由分组中间件")
	// 可以通过 ctx.Set 在请求上下文中设置值,后续的处理函数能够取到该值ctx.Set("username", "张三")
    // 调用该请求的剩余处理程序
	ctx.Next()
}
  1. 控制器获取值
func (c UserController) Index(ctx *gin.Context) {
    username, _ := ctx.Get("username")
    fmt.Println(username)
    ctx.String(http.StatusOK, "这是用户首页 111")
}

注意事项

gin 默认中间件

gin.Default()默认使用了 Logger 和 Recovery 中间件,其中:

  • Logger 中间件将日志写入 gin.DefaultWriter,即使配置了 GIN_MODE=release。
  • Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入500 响应码。

如果不想使用上面两个默认的中间件,可以使用 gin.New()新建一个没有任何默认中间件的路由。

gin 中间件中使用 goroutine

当在中间件或 handler 中启动新的 goroutine 时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())

r.GET("/", func(c *gin.Context) {
	cCp := c.Copy()
	go func() {
		// simulate a long task with time.Sleep(). 5 seconds
		time.Sleep(5 * time.Second)
		// 这里使用你创建的副本
		fmt.Println("Done! in path " + cCp.Request.URL.Path)
	}()
	c.String(200, "首页")
})

Model

介绍

如果我们的应用非常简单的话,我们可以在 Controller 里面处理常见的业务逻辑。

但是如果我们有一个功能想在多个控制器、或者多个模板里面复用的话,那么我们就可以把公共的功能单独抽取出来作为一个模块(Model)。

Model 是逐步抽象的过程,一般我们会在Model 里面封装一些公共的方法让不同 Controller 使用,也可以在 Model 中实现和数据库打交道

封装公共方法

新建modles/tools.go

package models

import (
	"crypto/md5"
	"fmt"
	"time"
)

// 时间戳间戳转换成日期
func UnixToDate(timestamp int) string {
	t := time.Unix(int64(timestamp), 0)
	return t.Format("2006-01-02 15:04:05")
}

// 日期转换成时间戳 2020-05-02 15:04:05
func DateToUnix(str string) int64 {
	template := "2006-01-02 15:04:05"
	t, err := time.ParseInLocation(template, str, time.Local)
	if err != nil {
		return 0
	}
	return t.Unix()
}
func GetUnix() int64 {
	return time.Now().Unix()
}
func GetDate() string {
	template := "2006-01-02 15:04:05"
	return time.Now().Format(template)
}
func GetDay() string {
	template := "20060102"
	return time.Now().Format(template)
}
func Md5(str string) string {
	data := []byte(str)
	return fmt.Sprintf("%x\n", md5.Sum(data))
}

控制器中调用

package controllers
import ( 
    "gin_demo/models"
)
day := models.GetDay()

全局函数

  1. 在models/tools.go中定义方法

    // 时间戳间戳转换成日期
    func UnixToDate(timestamp int64) string {
    	t := time.Unix(int64(timestamp), 0)
    	return t.Format("2006-01-02 15:04:05")
    }
    
  2. main.go

    //注册全局模板函数 注意顺序,注册模板函数需要在加载模板上面
    r := gin.Default()
    r.SetFuncMap(template.FuncMap{ 
        "unixToDate": models.UnixToDate, 
    })
    
  3. 控制器

    func (c UserController) Add(ctx *gin.Context) {
    	ctx.HTML(http.StatusOK, "admin/user/add.html", gin.H{ 
            "now": models.GetUnix(), 
        })
    }
    
  4. 在模板中使用

    {{.now | unixToDate}}

文件上传

参考文档

单文件上传

  1. 定义HTML模板

    需要在上传文件的 form 表单上面需要加入 enctype="multipart/form-data"

<!-- 相当于给模板定义一个名字 define end 成对出现-->
{{ define "admin/user/add.html" }}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
    <form action="/admin/user/doAdd" method="post" enctype="multipart/form-data">
        用户名: <input type="text" name="username" placeholder="用户名"> <br>
        <br>头 像:<input type="file" name="face"><br> <br>
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
{{ end }}
  1. 定义业务逻辑
func (c UserController) DoAdd(ctx *gin.Context) {
	username := ctx.PostForm("username")
	file, err := ctx.FormFile("face")
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
		return
	}
	// 上传文件到指定的目录
	dst := path.Join("./static/upload", file.Filename)
	fmt.Println(dst)
	ctx.SaveUploadedFile(file, dst)
	ctx.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("'%s' uploaded!", file.Filename), "username": username})
}

多文件上传

不同名多文件

  1. 定义模板 需要在上传文件的 form 表单上面需要加入 enctype="multipart/form-data"
<!-- 相当于给模板定义一个名字 define end 成对出现-->
{{ define "admin/user/add.html" }}
<!DOCTYPE html>
<html lang ="en">
<head>
    <meta charset = "UTF-8">
    <meta http-equiv = "X-UA-Compatible" content ="IE=edge">
    <meta name = "viewport" content = "width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
    <body>
    <form action ="/admin/user/doAdd" method = "post" enctype = "multipart/form-data">
    用户名: <input type = "text" name = "username" placeholder ="用户名"> <br> <br>头 像 1:<input type = "file" name = "face1"><br> <br>
    头 像 2:<input type = "file" name = "face2"><br> <br>
    <input type ="submit" value = "提交">
    </form>
</body>
</html>
{{ end }}
  1. 定义业务逻辑
func (c UserController) DoAdd(ctx *gin.Context) {
    username := ctx.PostForm("username")
    face1, err1 := ctx.FormFile("face1")
    face2, err2 := ctx.FormFile("face2")
    // 上传文件到指定的目录
    if err1 == nil {
    	dst1 := path.Join("./static/upload", face1.Filename)
    	ctx.SaveUploadedFile(face1, dst1)
    }
    if err2 == nil {
    	dst2 := path.Join("./static/upload", face2.Filename)
    	ctx.SaveUploadedFile(face2, dst2)
    }
    ctx.JSON(http.StatusOK, gin.H{
        "message": "文件上传成功", "username": username, 
    })
    // ctx.String(200, username)
}

同名多文件

参考

  1. 定义HTML模板

    需要在上传文件的 form 表单上面需要加入 enctype="multipart/form-data"

<!-- 相当于给模板定义一个名字 define end 成对出现-->
{{ define "admin/user/add.html" }}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
    <form action="/admin/user/doAdd" method="post" enctype="multipart/form-data">
        用户名: <input type="text" name="username" placeholder="用户名"> <br> <br>头 像 1:<input type="file"
                                                                                                 name="face[]"><br> <br>
        头 像 2:<input type="file" name="face[]"><br> <br>
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
{{ end }}
  1. 定义业务逻辑
func (c UserController) DoAdd(ctx *gin.Context) {
	username := ctx.PostForm("username")
	// Multipart form
	form, _ := ctx.MultipartForm()
	files := form.File["face[]"]
	// var dst;
	for _, file := range files {
		// 上传文件至指定目录
		dst := path.Join("./static/upload", file.Filename)
		ctx.SaveUploadedFile(file, dst)
	}
	ctx.JSON(http.StatusOK, gin.H{"message": "文件上传成功", "username": username})
}

Gin-Cookie

设置与获取

通过如下方法设置Cookie

c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)

一共有七个参数,对应如下

参数作用
keykey的名称
valuekey对应的value值的名称
maxAge过期时间.如果只想设置 Cookie 的保存路径而不想设置存活时间,可以在第三个参数中传递 nil
pathcookie 的路径
domaincookie 的路径 Domain 作用域 本地调试配置成 localhost , 正式上线配置成域名
secure当 secure 值为 true 时,cookie 在 HTTP 中是无效,在HTTPS 中才有效
httpOnly是微软对 COOKIE 做的扩展。如果在 COOKIE 中设置了“httpOnly”属性,则通过程序(JS 脚本、applet 等)将无法读取到 COOKIE 信息,防止XSS 攻击产生

获取Cookie

cookie, err := c.Cookie("name")

完整代码如下

package main

import (
	"gin_demo/models"
	"github.com/gin-gonic/gin"
	"html/template"
)

func main() {
	r := gin.Default()
	r.SetFuncMap(template.FuncMap{"unixToDate": models.UnixToDate})
	r.GET("/", func(c *gin.Context) {
		c.SetCookie("usrename", "张三", 3600, "/", "localhost", false, true)
		c.String(200, "首页")
	})
	r.GET("/user", func(c *gin.Context) {
		username, _ := c.Cookie("usrename")
		c.String(200, "用户-"+username)
	})
	r.Run(":8080")
}

共享Cookie

实现多个二级域名共享 cookie

  1. 分别把 a.itying.com 和 b.itying.com 解析到我们的服务器
  2. 我们想的是用户在 a.itying.com 中设置 Cookie 信息后在 b.itying.com 中获取刚才设置的cookie,也就是实现多个二级域名共享 cookie

这时候的话我们就可以这样设置 cookie

c.SetCookie("usrename", "张三", 3600, "/", ".itying.com", false, true)

Session

介绍

Gin 官方没有给我们提供 Session 相关的文档,这个时候我们可以使用第三方的Session中间件来实现。

gin-contrib/sessions 中间件支持的存储引擎:

  • cookie
  • memstore
  • redis
  • memcached
  • mongodb

安装Session包

go get github.com/gin-contrib/sessions

基本用法

package main

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

func main() {
	r := gin.Default()
	// 创建基于 cookie 的存储引擎,secret11111 参数是用于加密的密钥store := cookie.NewStore([]byte("secret11111"))
	// 设置 session 中间件,参数 mysession,指的是 session 的名字,也是cookie 的名字// store 是前面创建的存储引擎,我们可以替换成其他存储引擎r.Use(sessions.Sessions("mysession", store))
	r.GET("/", func(c *gin.Context) {
		//初始化 session 对象
		session := sessions.Default(c)
		//设置过期时间
		session.Options(sessions.Options{
			MaxAge: 3600 * 6, // 6hrs
		})
		//设置 Session
		session.Set("username", "张三")
		session.Save()
		c.JSON(200, gin.H{"msg": session.Get("username")})
	})
	r.GET("/user", func(c *gin.Context) {
		// 初始化 session 对象
		session := sessions.Default(c)
		// 通过 session.Get 读取 session 值
		username := session.Get("username")
		c.JSON(200, gin.H{"username": username})
	})
	r.Run(":8000")
}

在Redis中使用Session

安装Redis存储引擎包

go get github.com/gin-contrib/sessions/redis

用法

package main

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

func main() {
	r := gin.Default()
	// 初始化基于 redis 的存储引擎
	// 参数说明:
	// 第 1 个参数 - redis 最大的空闲连接数
	// 第 2 个参数 - 数通信协议 tcp 或者 udp
	// 第 3 个参数 - redis 地址, 格式,host:port
	// 第 4 个参数 - redis 密码
	// 第 5 个参数 - session 加密密钥
	store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
	r.Use(sessions.Sessions("mysession", store))
	r.GET("/", func(c *gin.Context) {
		session := sessions.Default(c)
		session.Set("username", "李四")
		session.Save()
		c.JSON(200, gin.H{"username": session.Get("username")})
	})
	r.GET("/user", func(c *gin.Context) {
		// 初始化 session 对象
		session := sessions.Default(c)
		// 通过 session.Get 读取 session 值
		username := session.Get("username")
		c.JSON(200, gin.H{"username": username})
	})
	r.Run(":8000")
}