# 环境搭建

Gin (opens new window) 是一个 Go 编写的轻量级 http web 框架,运行速度非常快。Gin Github 地址 (opens new window)

# 1、下载并安装 gin
go get -u github.com/gin-gonic/gin

# 2、将 gin 引入到代码中
import "github.com/gin-gonic/gin"

# 3、如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包
import "net/http"

# 4、新建 Main.go 配置路由
package main
import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default() // 创建一个默认的 Gin 引擎实例
	// gin.Context 是 Gin 框架中最核心的数据结构
	// 它封装了 HTTP 请求的完整上下文信息,包括请求的参数、路由信息、中间件等
	r.GET("/ping", func(c *gin.Context) {
		// gin.H:是 map[string]interface{} 的简写,用于构建 JSON 对象
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // 监听并在 0.0.0.0:8080 上启动服务  自定义端口 r.Run(":9000")
}
# 运行你的项目
go run main.go

# 程序的热加载

# 工具 1
go get github.com/pilu/fresh
D:\gin_demo>fresh

# 工具 2
go get -u github.com/codegangsta/gin
D:\gin_demo>gin run main.go

# 路由使用

# 路由配置

r.GET("网址", func(c *gin.Context) {
	c.String(200, "Get")
})

r.POST("网址", func(c *gin.Context) {
	c.String(200, "POST")
})

r.PUT("网址", func(c *gin.Context) {
	c.String(200, "PUT")
})

r.DELETE("网址", func(c *gin.Context) {
	c.String(200, "DELETE")
})

# 路由组

新建 routes 文件夹,routes 文件下面新建 adminRoutes.go、apiRoutes.go、defaultRoutes.go

  • 新建 adminRoutes.go
package routes

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

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")
		})
	}
}
  • 新建 apiRoutes.go
package routes

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

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": "这是新闻"})
		})
	}
}
  • 新建 defaultRoutes.go
package routes

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

func DefaultRoutesInit(router *gin.Engine) {
	defaultRoute := router.Group("/")
	{
		defaultRoute.GET("/", func(c *gin.Context) {
			c.String(200, "首页")
		})
	}
}
  • 配置 main.go
func main() {
	r := gin.Default()
	routes.AdminRoutesInit(r)
	routes.ApiRoutesInit(r)
	routes.DefaultRoutesInit(r)
	r.Run(":8080")
}

# 控制器相关

# 控制器分组

当我们的项目比较大的时候有必要对我们的控制器进行分组

  • 新建 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, "新闻首页")
}
func (c NewsController) Show(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, "增加用户")
}
  • 配置对应的路由 --adminRoutes.go
package routes

import (
	"test-project/controller/admin"
	"github.com/gin-gonic/gin"
)

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{}.Index)
		adminRouter.GET("/news/show", admin.NewsController{}.Show)
	}
}

# 控制器的继承

  • 新建 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, "失败")
}
  • NewsController 继承 BaseController
package admin

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

type NewsController struct {
	BaseController
}

func (c NewsController) Index(ctx *gin.Context) {
	c.Success(ctx)
}
func (c NewsController) Show(ctx *gin.Context) {
	c.Success(ctx)
}

# 请求响应

# 路由里面获取 Get 传值

r.GET("/news", func(c *gin.Context) {
	// user?uid=20&page=1
	uid := c.Query("uid")
	page := c.DefaultQuery("page", "0")
	c.String(200, "uid=%v page=%v", uid, page)
})

# 动态路由

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

# 获取 form 表单数据

r.POST("/doAddUser", func(c *gin.Context) {
	username := c.PostForm("username")
	password := c.PostForm("password")
	c.JSON(200, gin.H{
		"username": username, 
		"password": password
	})
})

# 返回数据

// 返回一个字符串
r.GET("/news", func(c *gin.Context) {
	aid := c.Query("aid")
	c.String(200, "aid=%s", aid)
})

// 返回一个 JSON 数据
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 = "IT 营学院" 
	msg.Message = "Hello world!" 
	msg.Age = 18
	c.JSON(http.StatusOK, msg)
})

// JSOPN
r.GET("/JSONP", func(c *gin.Context) {
	data := map[string]interface{}{ 
		"foo": "bar", 
	}
	// JSONP?callback=x
	// 将输出:x({\"foo\":\"bar\"})
	c.JSONP(http.StatusOK, data)
})

// 返回 XML 数据
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 = "IT 营学院" 
	msg.Message = "Hello world!" 
	msg.Age = 18
	c.XML(http.StatusOK, msg)
})

# 数据绑定到结构体

//注意首字母大写
type Userinfo struct {
	Username string `form:"username" json:"user"` 
	Password string `form:"password" json:"password"` 
}
  • Get 传值绑定到结构体
// ?username=zhangsan&password=123456
r.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 传值绑定到结构体
r.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()})
	}	
})

# 获取 Post Xml 数据

<?xml version="1.0" encoding="UTF-8"?>
<article>
	<content type="string">我是张三</content>
	<title type="string">张三</title>
</article>
type Article struct {
	Title string `xml:"title"`
	Content string `xml:"content"`
}
r.POST("/xml", func(c *gin.Context) {
	b, _ := c.GetRawData() // 从 c.Request.Body 读取请求数据
	article := &Article{}
	if err := xml.Unmarshal(b, &article); err == nil {
		c.JSON(http.StatusOK, article)
	} else {
		c.JSON(http.StatusBadRequest, err.Error())
	}
})

# 中间件

# 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")
}
  • 在路由分组中配置中间件
// 写法一
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) {...})
}
  • 中间件里面加上 ctx.Next() 可以让我们在路由匹配完成后执行一些操作
func initMiddleware(ctx *gin.Context) {
	fmt.Println("1-执行中中间件")
	start := time.Now().UnixNano()
	// 调用请求后的剩余处理程序
	ctx.Next()
	fmt.Println("3-程序执行完成 计算时间")
	end := time.Now().UnixNano()
	fmt.Println(end - start)
}
  • 一个路由配置多个中间件的执行顺序
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-执行中中间件
}
  • ctx.Abort() 表示终止调用该请求的剩余处理程序
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-执行中中间件
}

# 全局中间件








 







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) {
		fmt.Println("执行路由里面的程序")
		ctx.String(200, "首页--中间件演示")
	})
	r.Run(":8090")
}

# 中间件和对应控制器之间共享数据

  • 中间件设置值



 



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

# gin 默认中间件

r := gin.Default() 
# 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, "首页")
})

# 自定义方法

  • 新建 utils/ 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 Md5(str string) string {
	data := []byte(str)
	return fmt.Sprintf("%x\n", md5.Sum(data))
}
  • 控制器中调用 Model
package controllers
import "gin_demo/models"

day := models.GetDay()

# Golang Md5 加密

  • 打开 golang 包对应的网站:https://pkg.go.dev/,搜索 md5
// 方法一
data := []byte("123456")
has := md5.Sum(data)
md5str := fmt.Sprintf("%x", has)
fmt.Println(md5str)

// 方法二
h := md5.New()
io.WriteString(h, "123456")
fmt.Printf("%x\n", h.Sum(nil))

# 文件上传

注意

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

  • 单文件上传


 








 

 






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, 
	})
}
  • 多文件上传--不同名字的多个文件
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, 
	})
}
  • 多文件上传--相同名字的多个文件


 
 



<form action="/admin/user/doAdd" method="post" enctype="multipart/form-data">
用户名: <input type="text" name="username" placeholder="用户名">
头 像 1:<input type="file" name="face[]"><br> <br>
头 像 2:<input type="file" name="face[]"><br> <br>
<input type="submit" value="提交">
</form>



 
 

 







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, })
}
  • 文件上传 按照日期存储
func (c UserController) DoAdd(ctx *gin.Context) {
	username := ctx.PostForm("username")
	//1、获取上传的文件
	file, err1 := ctx.FormFile("face")
	if err1 == nil {
		//2、获取后缀名 判断类型是否正确 .jpg .png .gif .jpeg
		extName := path.Ext(file.Filename)
		allowExtMap := map[string]bool{ 
			".jpg": true, 
			".png": true, 
			".gif": true, 
			".jpeg": true, 
		}
		if _, ok := allowExtMap[extName]; !ok {
			ctx.String(200, "文件类型不合法")
			return
		}
		//3、创建图片保存目录 static/upload/20200623
		day := models.GetDay()
		dir := "./static/upload/" + day
		if err := os.MkdirAll(dir, 0666); err != nil {
			log.Error(err)
		}
		//4、生成文件名称 144325235235.png
		fileUnixName := strconv.FormatInt(models.GetUnix(), 10)
		//static/upload/20200623/144325235235.png
		saveDir := path.Join(dir, fileUnixName+extName)
		ctx.SaveUploadedFile(file, saveDir)
	}
	ctx.JSON(http.StatusOK, gin.H{ "message": "文件上传成功", "username": username, })
	// ctx.String(200, username)
}
func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		// Cookie 名称、值、过期时间、路径、域名、是否仅 HTTPS、是否禁止 JS 访问
		c.SetCookie("usrename", "张三", 3600, "/", "127.0.0.1:8090", false, true)
		c.String(200, "首页")
	})
	r.GET("/user", func(c *gin.Context) {
		username, _ := c.Cookie("usrename")
		c.String(200, "用户-"+username)
	})
	r.Run(":8090")
}

# 使用 Session

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

gin-contrib/sessions 中间件支持的存储引擎:
# cookie
# memstore
# redis
# memcached
# mongodb
  • 基于 Cookie 存储 Session
// 安装 session 包:go get github.com/gin-contrib/sessions

package main
import ( 
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"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")
}
  • 基于 Cookie 存储 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")
}

# go-ini 介绍

go-ini (opens new window) 是地表 最强大、最方便 和 最流行 的 Go 语言INI 文件操作库

  • 新建 conf/app.ini
app_name = itying gin

# possible values: DEBUG, INFO, WARNING, ERROR, FATAL
log_level = DEBUG

[mysql]
ip = 192.168.0.6
port = 3306
user = root
password = 123456
database = gin

[redis]
ip = 127.0.0.1
port = 6379
  • 操作刚才创建的配置文件
import ( 
	"fmt"
	"os"
	"gopkg.in/ini.v1"
)
func main() {
	cfg, err := ini.Load("./conf/app.ini")
	if err != nil {
		fmt.Printf("Fail to read file: %v", err)
		os.Exit(1)
	}
	// 典型读取操作,默认分区可以使用空字符串表示
	fmt.Println("App Mode:", cfg.Section("").Key("app_name").String())
	
	// 差不多了,修改某个值然后进行保存
	cfg.Section("").Key("app_name").SetValue("itying gin")
	cfg.SaveTo("./conf/app.ini")
	
	// 读取 mysql 配置
	ip := cfg.Section("mysql").Key("ip").String()
	port := cfg.Section("mysql").Key("port").String()
	user := cfg.Section("mysql").Key("user").String()
	password := cfg.Section("mysql").Key("password").String()
	database := cfg.Section("mysql").Key("database").String()
	dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local ", 
	                    user, password, ip, port, database)
	fmt.Println(dsn)
}