# 环境搭建

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() {
	gin.SetMode(value: "release") // 设置模式
	
	r := gin.Default() // 创建一个默认的 Gin 引擎实例
	// 给本地文件夹起了一个公开的“别名”
	r.Static(relativePath: "/uploads", root: "upoads")
	// gin.Context 是 Gin 框架中最核心的数据结构
	// 它封装了 HTTP 请求的完整上下文信息,包括请求的参数、路由信息、中间件等
	r.GET("/ping", func(c *gin.Context) {
		// gin.H:是 map[string]interface{} 的简写,用于构建 JSON 对象
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	// 内部就是启动了一个标准的 HTTP 服务,并持续监听端口,直到收到退出信号或发生致命错误
	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

# 如果遇到的问题:
# 使用fresh命令,总会遇到:不是内部或外部命令,也不是可运行的程序或批处理文件
# 或者“command not found:fresh”
# 解决方案:打开任意终端,用go env命令查看,将GO111MODULE改成GO111MODULE=on
go env -w GO111MODULE=on
go install github.com/pilu/fresh@latest
go get github.com/pilu/fresh


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

# 路由使用

# 路由配置

// GET - 获取资源(查)
r.GET("/users", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "获取用户列表"})
})

// POST - 创建资源(增)
r.POST("/users", func(c *gin.Context) {
    c.JSON(201, gin.H{"message": "创建用户"})
})

// PUT - 更新资源(改)
r.PUT("/users/1", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "更新用户"})
})

// DELETE - 删除资源(删)
r.DELETE("/users/1", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "删除用户"})
})

// r.Any() - 匹配所有 HTTP 方法
// 不管 GET、POST、PUT... 来了都接
r.Any("/any", func(c *gin.Context) {
    c.JSON(200, gin.H{
        "method":  c.Request.Method, // 告诉你用的什么方法
        "message": "来者不拒",
    })
})

// r.Handle() - 通用写法,方法名用字符串
r.Handle("GET", "/custom", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "自定义路由"})
})

// 于直接写入原始字节数据作为 HTTP 响应
r.Handle("GET", "/hello", func(c *gin.Context) {
    ctx.Data(http.StatusOK, "", "hello")
})

// 重定向
r.GET("/", func(c *gin.Context) {
	// 302
	c.Redirect(http.StatusFound, "/web")
})

// 404
r.NoRoute(func(c *gin.Context) {
	c.File("../../../frontend/www/web/index.html")
})

# 路由组

新建 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 传值

// c.Query() - 直接拿值,没有返回空字符串
r.GET("/welcome", func(c *gin.Context) {
    // 访问 /welcome?firstname=张&lastname=三
    firstname := c.Query("firstname") // "张"
    // c.DefaultQuery() - 没传就给个默认值
    lastname := c.DefaultQuery("lastname", "Guest") // 传了就是传的值,没传就是 "Guest"
    c.JSON(200, gin.H{
        "firstname": firstname,
        "lastname":  lastname,
    })
})

// c.GetQuery() - 需要知道参数到底存不存在
r.GET("/search", func(c *gin.Context) {
    // 返回 (值, 是否存在) 的双值
    keyword, exists := c.GetQuery("keyword")
    if !exists {
        c.JSON(400, gin.H{"error": "请提供 keyword 参数"})
        return
    }
    page := c.DefaultQuery("page", "1") // 没传默认第1页
    c.JSON(200, gin.H{
        "keyword": keyword,
        "page":    page,
    })
})

// c.QueryArray() - 同名参数有多个值
// 访问 /filter?color=红&color=蓝&color=绿
r.GET("/filter", func(c *gin.Context) {
    colors := c.QueryArray("color") // ["红", "蓝", "绿"]
    c.JSON(200, gin.H{"colors": colors})
})

# 动态路由

// 路由定义:/user/:name
// :name 就是一个"占位符",能匹配任意值
r.GET("/user/:name", func(c *gin.Context) {
    // c.Param() 取出路径参数
    name := c.Param("name") // 访问 /user/张三 → name = "张三"
    c.JSON(200, gin.H{
        "message": "你好",
        "name":    name,
    })
})

// 多个路径参数
r.GET("/user/:name/:action", func(c *gin.Context) {
    name := c.Param("name")     // /user/张三/posts → name = "张三"
    action := c.Param("action") // /user/张三/posts → action = "posts"
    c.JSON(200, gin.H{
        "name":   name,
        "action": action,
    })
})

// *action 表示通配符路径参数,它会匹配从当前位置开始的所有剩余路径段(包括斜杠 /)
// 匹配:/user/   /user/profile   /user/profile/edit  不匹配:/user
r.GET("/user/*action", func(c *gin.Context) {
	// 注意:通配符的值带着前导的 "/"
	filepath := c.Param("filepath") // /files/report.pdf → filepath = "/report.pdf"
	c.JSON(200, gin.H{"filepath": filepath})
})

# 获取 form 表单数据

// c.PostForm() - 获取表单字段
r.POST("/login", func(c *gin.Context) {
    username := c.PostForm("username") // 接收表单中的 username 字段
    password := c.PostForm("password") // 接收表单中的 password 字段
    // c.DefaultPostForm() - 提供默认值
    remember := c.DefaultPostForm("remember", "false") // 没传就默认 "false"
    c.JSON(200, gin.H{
        "username": username,
        "password": password,
        "remember": remember,
    })
})

// c.PostFormArray() - 同名字段有多个值
r.POST("/survey", func(c *gin.Context) {
    hobbies := c.PostFormArray("hobby") // 接收多个 hobby
    city := c.DefaultPostForm("city", "北京")
    c.JSON(200, gin.H{
        "hobbies": hobbies,
        "city":    city,
    })
})

# 返回数据

// 返回一个字符串
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()
	// c.AbortWithStatusJSON(401, gin.H{"error": "未登录"}) 拦截并返回 JSON
	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()
	// mian 方法会等待 goroutine 执行完毕
	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, 
	})
}
  • 多文件上传--相同名字的多个文件
func main() {
    r := gin.Default()
    r.MaxMultipartMemory = 32 << 20// 多文件上传,缓冲区设大一点:32MB

    // 上传页面
    r.GET("/", func(c *gin.Context) {
        c.Header("Content-Type", "text/html; charset=utf-8")
        c.String(http.StatusOK, `
            <h2>多文件上传</h2>
            <!-- multiple 属性允许一次选多个文件 -->
            <form method="POST" action="/upload" enctype="multipart/form-data">
                <p><input type="file" name="files" multiple></p>
                <p><button type="submit">上传全部</button></p>
            </form>
        `)
    })

    // 处理多文件上传
    r.POST("/upload", func(c *gin.Context) {
        // 用 c.MultipartForm() 获取整个表单
        form, err := c.MultipartForm()
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }

        // 从表单中取出 "files" 字段对应的文件列表
        files := form.File["files"]  // 注意:这里是 form.File,不是 c.FormFile
        iflen(files) == 0 {
            c.JSON(http.StatusBadRequest, gin.H{"error": "请选择文件"})
            return
        }

        // 确保上传目录存在
        uploadDir := "./uploads"
        os.MkdirAll(uploadDir, 0755)

        // 遍历每个文件,逐个保存
        var uploaded []gin.H
        for _, file := range files {
            // 给每个文件生成唯一名字
            newName := fmt.Sprintf("%d_%s", time.Now().UnixNano(), file.Filename)
            dst := filepath.Join(uploadDir, newName)

            if err := c.SaveUploadedFile(file, dst); err != nil {
                uploaded = append(uploaded, gin.H{
                    "name":  file.Filename,
                    "error": err.Error(),
                })
                continue
            }

            uploaded = append(uploaded, gin.H{
                "name": file.Filename,
                "size": file.Size,
                "path": "/files/" + newName,
            })
        }

        c.JSON(http.StatusOK, gin.H{
            "message": fmt.Sprintf("成功上传 %d 个文件", len(uploaded)),
            "files":   uploaded,
        })
    })
}
  • 文件上传 按照日期存储
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")
}

# gin.Context 上下文

Gin 会为每一个新接入的 HTTP 请求启动一个独立的 Goroutine 来处理,这是基于 Go 语言 net/http 标准库的标准行为,也是 Gin 能够高效处理高并发的核心原因

# 上下文生命周期

# 每个请求的 *gin.Context 对象会被框架复用以提高性能,而不是为每个请求都创建一个全新的对象
# 请求结束后,*gin.Context 会被放回内存池中,供下一个请求使用
# 因此在 Goroutine 中要访问 *gin.Context,必须使用 c.Copy() 方法来获取一个安全的只读副本
# 这样做是必要的,可以避免直接使用原始 *gin.Context 可能导致的数据竞态
r.GET("/long_task", func(c *gin.Context) {
    // ✅ 在启动新的 goroutine 前,显式调用 c.Copy() 创建副本
    cCp := c.Copy() 
    go func() {
        // 模拟耗时操作
        time.Sleep(5 * time.Second)
        // ✅ 使用安全的副本 cCp 来访问请求信息
        log.Println("任务完成,路径: " + cCp.Request.URL.Path)
    }()
    c.JSON(200, gin.H{"message": "请求已接收,后台处理中"})
})

# 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)
}