# 环境搭建
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)
}
# 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")
}
# 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)
}
GORM →