# 环境搭建
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)
}
# Cookie 和 Session
# 使用 Cookie
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)
}
GORM →