Gin学习笔记

RESTful API

以前写网站

  • get /user

  • post /create_user

  • post /update_user

  • post /delete_user

RESTful API

  • get /user 获取

  • post /user 新建

  • put /user 更新

  • patch /user 更新部分

  • delete /user 删除

  • REST 与技术无关,代表的是一种软件架构风格只要API程序遵循了REST风格,那就可以称其为RESTful API

  • REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作

    • get

    • post

    • put

    • patch

    • delete

  • Gin框架支持开发RESTful API的开发

复制代码
package main

import (
	"net/http"

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

func main() {
	// 创建一个服务
	ginServer := gin.Default()
	// 访问地址,处理请求 Request Response
	ginServer.GET("/hello", func(context *gin.Context) {
		context.JSON(200, gin.H{"msg": "hello,world"})
	})
	ginServer.POST("/user", func(ctx *gin.Context) {
		ctx.JSON(http.StatusOK, gin.H{"msg": "post请求"})
	})
	ginServer.PUT("/user")
	ginServer.DELETE("/user")

	// 服务器端口
	err := ginServer.Run(":8082")
    // http.ListenAndServe(":8082", ginServer)
	if err != nil {
		return
	}
}

第一个Gin示例

复制代码
// cmd
go get -u github.com/gin-gonic/gin
复制代码
package main

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

func main() {
    // 	gin.SetMode(gin.ReleaseMode) // 切换到生产模式
	// 创建一个服务
	ginServer := gin.Default()
	// 访问地址,处理请求 Request Response
	ginServer.GET("/hello", func(context *gin.Context) {
		context.JSON(200, gin.H{"msg": "hello,world"})
        // http.StatusOK就是请求已经成功的200的状态码
	})

	// 服务器端口
    err := ginServer.Run(":8082")
    if err != nil {
		return
	}
}
  • 想要更改左上角的图标

    复制代码
    package main
    
    import (
    	"github.com/gin-gonic/gin"
        "github.com/thinkerou/favicon" // go get
    )
    
    func main() {
        // 	gin.SetMode(gin.ReleaseMode) // 切换到生产模式
    	// 创建一个服务
    	ginServer := gin.Default()
        ginServer.Use(favicon.New("./favicon.ico"))
    	// 访问地址,处理请求 Request Response
    	ginServer.GET("/hello", func(context *gin.Context) {
    		context.JSON(200, gin.H{"msg": "hello,world"})
    	})
    
    	// 服务器端口
    	ginServer.Run(":8082")
    }
  • IP变为内网IP

    复制代码
    package main
    
    import (
    	"net/http"
    
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
        // 	gin.SetMode(gin.ReleaseMode) // 切换到生产模式
    	router := gin.Default()
    	router.GET("/index", func(ctx *gin.Context) {
    		ctx.String(200, "Hello")
    	})
    	// 启动监听,gin会把web服务运行在本机的0.0.0.0:8080端口上
    	router.Run("0.0.0.0:8082")
    	// 用原生http服务的方式, router.Run本质就是http.ListenAndServe的进一步封装
    	http.ListenAndServe(":8082", router)
    }

加载静态页面

复制代码
package main

import (
	"net/http"

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

func main() {
    // 	gin.SetMode(gin.ReleaseMode) // 切换到生产模式
	// 创建一个服务
	ginServer := gin.Default()
	// ginServer.Use(favicon.New("./favicon.ico"))

	// 加载静态页面
	ginServer.LoadHTMLGlob("templates/*")
	// ginServer.LoadHTMLFiles("templates/index.html")
	// LoadHTMLGlob是全局加载Files是指定文件

	// 响应一个页面给前端
	ginServer.GET("/index", func(context *gin.Context) {
		// context.JSON() json数据
		context.HTML(http.StatusOK, "index.html", gin.H{
			"msg": "这是go后台传入的数据",
		})
	})

	// 服务器端口
	err := ginServer.Run(":8082")
	if err != nil {
		return
	}
}
  • 新建一个文件夹templates,在其下面创建index.html文件

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>一个GoWeb页面</title>
</head>
<body>
    <h1>感谢大家支持江小年的博客</h1>
    获取后端的数据为:
    {{.msg}}
</body>
</html>

加载资源包

  • 创建static文件夹

    • 在其中创建css文件夹

      • style.css
    • js文件夹

      • common.js
复制代码
package main

import (
	"net/http"

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

func main() {
    // 	gin.SetMode(gin.ReleaseMode) // 切换到生产模式
	// 创建一个服务
	ginServer := gin.Default()
	// ginServer.Use(favicon.New("./favicon.ico"))

	// 加载静态页面
	ginServer.LoadHTMLGlob("templates/*")
	// ginServer.LoadHTMLFiles("templates/index.html")
	// LoadHTMLGlob是全局加载Files是指定文件

	//加载资源文件
	ginServer.Static("/static", "./static")

	// 响应一个页面给前端
	ginServer.GET("/index", func(context *gin.Context) {
		// context.JSON() json数据
		context.HTML(http.StatusOK, "index.html", gin.H{
			"msg": "这是go后台传入的数据",
            "title": "你猜"
		})
	})

	// 服务器端口
	err := ginServer.Run(":8082")
	if err != nil {
		return
	}
}
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>一个GoWeb页面</title>
    <link rel="stylesheet" href="/static/css/style.css">
    <script src="/static/js/common.js"></script>
</head>
<body>
    <h1>感谢大家支持江小年的博客</h1>
    获取后端的数据为:
    {{.msg}}
    title: 
    {{.title}}
</body>
</html>
复制代码
package main

import (
	"net/http"

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

func main() {
	gin.SetMode(gin.ReleaseMode)

	ginServer := gin.Default()

	ginServer.LoadHTMLFiles("static/index.html")
	ginServer.Static("/css", "static/css")
	ginServer.Static("/font", "static/font")

	ginServer.GET("index", func(ctx *gin.Context) {
		ctx.HTML(http.StatusOK, "index.html", gin.H{
			"msg": "jiangxiaonian",
		})
	})

	err := ginServer.Run(":8080")
	if err != nil {
		return
	}
}

获取参数

获取请求参数

复制代码
// usl?userid=XXX&username=jaingxionian
// /user/info/1/jiangxiaonian
复制代码
// main.go
package main

import (
	"net/http"

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

func main() {
	// 创建一个服务
	ginServer := gin.Default()
	// ginServer.Use(favicon.New("./favicon.ico"))

	// 加载静态页面
	ginServer.LoadHTMLGlob("templates/*")
	// ginServer.LoadHTMLFiles("templates/index.html")
	// LoadHTMLGlob是全局加载Files是指定文件

	//加载资源文件
	ginServer.Static("/static", "./static")

	// 响应一个页面给前端
	ginServer.GET("/index", func(context *gin.Context) {
		// context.JSON() json数据
		context.HTML(http.StatusOK, "index.html", gin.H{
			"msg": "这是go后台传入的数据",
		})
	})

	// usl?userid=XXX&username=jaingxionian
	ginServer.GET("/user/info", func(context *gin.Context) {
		userid := context.Query("userid")
        username := context.Query("username")

        // fmt.Println(c.QueryArray("userid")) // 拿到多个相同的查询参数
        // fmt.Println(c.DefaultQuery("userid", 0))
        
		context.JSON(http.StatusOK, gin.H{
			"userid":   userid,
			"username": username,
		})
	})

	// /user/info/1/jiangxiaonian
	// 只要:后名字正确就能匹配上
	ginServer.GET("/user/info/:userid/:username", func(context *gin.Context) {
		userid := context.Param("userid")
		username := context.Param("username")
		context.JSON(http.StatusOK, gin.H{
			"userid":   userid,
			"username": username,
		})
	})

	// 服务器端口
	err := ginServer.Run(":8082")
	if err != nil {
		return
	}
}
// 运行后访问 http://localhost:8082/user/info?userid=1&username=jaingxiaonain

获取前端给后端传递的json(序列化)参数

复制代码
// gin 优化过的BindJSON
package controllers

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

type OrderController struct{}

func (o OrderController) GetList(c *gin.Context) {
	m := make(map[string]interface{})
	err := c.BindJSON(&m)
	if err == nil {
		c.JSON(http.StatusOK, m)
		return
	}
	c.JSON(4001, gin.H{"err": err})
}
复制代码
// 结构体
package controllers

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

type OrderController struct{}

type Search struct {
	Name string `json:"name"`
	Cid  int    `json:"cid"`
}

func (o OrderController) GetList(c *gin.Context) {
	search := &Search{}
	err := c.BindJSON(&search)
	if err == nil {
		ReturnSuccess(c, 0, search.Name, search.Cid, 1)
		return
	}
	ReturnErrer(c, 4001, gin.H{"err": err})
}
复制代码
// 未优化的*gin.Context.GetRawData()  json.Unmarshal()
// 前段给后端传JSON
ginServer.POST("/json", func(context *gin.Context) {
	// GetRawData() 从请求体(request.body)里获取对象
	b, _ := context.GetRawData()
	var m map[string]interface{}
	// 包装为json数据 []byte
	_ = json.Unmarshal(b, &m)
	context.JSON(http.StatusOK, m)
})

获取表单中的参数

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>一个GoWeb页面</title>
    <link rel="stylesheet" href="/static/css/style.css">
    <script src="/static/js/common.js"></script>
</head>
<body>
    <h1>感谢大家支持江小年的博客</h1>
    <form action="/user/add" method="post">
        <p>username: <input type="text" name="username"></p>
        <p>password: <input type="password" name="password"></p>
        <button type="submit"> 提交 </button>
    </form>
</body>
</html>
复制代码
// .DefaultPostForm()  .PostForm()
package main

import (
	"encoding/json"
	"net/http"

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

func main() {
	// 创建一个服务
	ginServer := gin.Default()
	// ginServer.Use(favicon.New("./favicon.ico"))
	// 加载静态页面
	ginServer.LoadHTMLGlob("templates/*")
	// ginServer.LoadHTMLFiles("templates/index.html")
	// LoadHTMLGlob是全局加载Files是指定文件
	//加载资源文件
	ginServer.Static("/static", "./static")
	// 响应一个页面给前端
	ginServer.GET("/index", func(context *gin.Context) {
		// context.JSON() json数据
		context.HTML(http.StatusOK, "index.html", gin.H{
			"msg": "这是go后台传入的数据",
		})
	})

	// 表单
	ginServer.POST("/user/add", func(context *gin.Context) {
		username := context.PostForm("username")
		password := context.PostForm("password")
        // password := context.DefaultPostForm("password", 12345)第二个参数为默认值

		// 加判断逻辑代码

		context.JSON(http.StatusOK, gin.H{
			"msg":      "ok",
			"username": username,
			"password": password,
		})
	})

	// 服务器端口
	err := ginServer.Run(":8082")
	if err != nil {
		return
	}
}

路由

HTTP重定向

复制代码
package main

import (
	"encoding/json"
	"net/http"

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

func main() {
	// 创建一个服务
	ginServer := gin.Default()
	// 响应一个页面给前端
	ginServer.GET("/index", func(context *gin.Context) {
		// context.JSON() json数据
		context.HTML(http.StatusOK, "index.html", gin.H{
			"msg": "这是go后台传入的数据",
		})
	})

	// 路由
	ginServer.GET("/test", func(context *gin.Context) {
		// 重定向    StatusMovedPermanently 301
		context.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
	})

	// 服务器端口
	err := ginServer.Run(":8082")
	if err != nil {
		return
	}
}

路由重定向

路由重定向,使用HandleContext

复制代码
package main

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

func main() {
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		// 指定重定向的URL
		c.Request.URL.Path = "/test2"
		r.HandleContext(c)
	})
	r.GET("/test2", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"hello": "world"})
	})
	// Listen and serve on 0.0.0.0:8080
	err := r.Run(":8080")
	if err != nil {
		return
	}
}

404 NoRoute()

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>404</title>
</head>
<body>
    
    <h1>江小年的404页面</h1>
    
</body>
</html>
复制代码
package main

import (
	"encoding/json"
	"net/http"

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

func main() {
	// 创建一个服务
	ginServer := gin.Default()
	// 路由
	ginServer.GET("/test", func(context *gin.Context) {
		// 重定向    StatusMovedPermanently 301
		context.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
	})

	// 404 NoRoute
	ginServer.NoRoute(func(context *gin.Context) {
		context.HTML(http.StatusNotFound, "404.html", nil)
	})

	// 服务器端口
	err := ginServer.Run(":8082")
	if err != nil {
		return
	}
}

路由组

复制代码
package main

import (
	"encoding/json"
	"net/http"

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

func main() {
	// 创建一个服务
	ginServer := gin.Default()
	// ginServer.Use(favicon.New("./favicon.ico"))

	// 加载静态页面
	ginServer.LoadHTMLGlob("templates/*")
	// ginServer.LoadHTMLFiles("templates/index.html")
	// LoadHTMLGlob是全局加载Files是指定文件

	//加载资源文件
	ginServer.Static("/static", "./static")

	// 响应一个页面给前端
	ginServer.GET("/index", func(context *gin.Context) {
		// context.JSON() json数据
		context.HTML(http.StatusOK, "index.html", gin.H{
			"msg": "这是go后台传入的数据",
		})
	})

	// usl?userid=XXX&username=jaingxionian
	ginServer.GET("/user/info", func(context *gin.Context) {
		userid := context.Query("userid")
		username := context.Query("username")
		context.JSON(http.StatusOK, gin.H{
			"userid":   userid,
			"username": username,
		})
	})

	// /user/info/1/jiangxiaonian
	// 只要:后名字正确就能匹配上
	ginServer.GET("/user/info/:userid/:username", func(context *gin.Context) {
		userid := context.Param("userid")
		username := context.Param("username")
		context.JSON(http.StatusOK, gin.H{
			"userid":   userid,
			"username": username,
		})
	})

	// 前段给后端传JSON
	ginServer.POST("/json", func(context *gin.Context) {
		// GetRawData() 从请求体(request.body)里获取对象
		b, _ := context.GetRawData()
		var m map[string]interface{}
		// 包装为json数据 []byte
		_ = json.Unmarshal(b, &m)
		context.JSON(http.StatusOK, m)
	})

	// 表单
	ginServer.POST("/user/add", func(context *gin.Context) {
		username := context.PostForm("username")
		password := context.PostForm("password")

		// 加判断逻辑代码

		context.JSON(http.StatusOK, gin.H{
			"msg":      "ok",
			"username": username,
			"password": password,
		})
	})

	// 路由
	ginServer.GET("/test", func(context *gin.Context) {
		// 重定向    StatusMovedPermanently 301
		context.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
	})

	// 404 NoRoute
	ginServer.NoRoute(func(context *gin.Context) {
		context.HTML(http.StatusNotFound, "404.html", nil)
	})

	// 路由组
	userGroup := ginServer.Group("/user")
	{
		userGroup.GET("/add")    // /user/add
		userGroup.GET("/login")  // /user/add
		userGroup.GET("/logout") // /user/add
	}

	orderGroup := ginServer.Group("/order")
	{
		orderGroup.GET("add")
		orderGroup.GET("delte")

	}

	// 服务器端口
	err := ginServer.Run(":8082")
	if err != nil {
		return
	}
}

路由嵌套

复制代码
shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/index", func(c *gin.Context) {...})
		shopGroup.GET("/cart", func(c *gin.Context) {...})
		shopGroup.POST("/checkout", func(c *gin.Context) {...})
		// 嵌套路由组
		xx := shopGroup.Group("xx")
		xx.GET("/oo", func(c *gin.Context) {...})
    }

中间件(Java中为拦截器)

复制代码
package main

import (
	"encoding/json"
	"log"
	"net/http"

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

// 自定义Go中间件 拦截器
func myHandler() gin.HandlerFunc {
	return func(context *gin.Context) {
		// Set一些值用作全局变量
		// 通过自定义的中间件,设置的值,在后续处理只要调用了这个中间件的都可以拿到这里的参数
		context.Set("usersession", "userid-1")
		context.Next() // 放形
		/* if XXX {
			context.Next() // 放形
		}
		context.Abort() // 阻止
		// 注册未指定就是全局使用
		*/
	}
	/* 46行加入中间件 */
}

func main() {
	// 创建一个服务
	ginServer := gin.Default()
	// ginServer.Use(favicon.New("./favicon.ico"))

	// 加载静态页面
	ginServer.LoadHTMLGlob("templates/*")
	// ginServer.LoadHTMLFiles("templates/index.html")
	// LoadHTMLGlob是全局加载Files是指定文件

	//加载资源文件
	ginServer.Static("/static", "./static")

	// 响应一个页面给前端
	ginServer.GET("/index", func(context *gin.Context) {
		// context.JSON() json数据
		context.HTML(http.StatusOK, "index.html", gin.H{
			"msg": "这是go后台传入的数据",
		})
	})

	// usl?userid=XXX&username=jaingxionian
	ginServer.GET("/user/info", myHandler(), func(context *gin.Context) {

		// 取出中间件中的值
		usersession := context.MustGet("usersession").(string)
		log.Println("========>", usersession) // 前端控制台输出

		userid := context.Query("userid")
		username := context.Query("username")
		context.JSON(http.StatusOK, gin.H{
			"userid":   userid,
			"username": username,
		})
	})

	// /user/info/1/jiangxiaonian
	// 只要:后名字正确就能匹配上
	ginServer.GET("/user/info/:userid/:username", func(context *gin.Context) {
		userid := context.Param("userid")
		username := context.Param("username")
		context.JSON(http.StatusOK, gin.H{
			"userid":   userid,
			"username": username,
		})
	})

	// 前段给后端传JSON
	ginServer.POST("/json", func(context *gin.Context) {
		// GetRawData() 从请求体(request.body)里获取对象
		b, _ := context.GetRawData()
		var m map[string]interface{}
		// 包装为json数据 []byte
		_ = json.Unmarshal(b, &m)
		context.JSON(http.StatusOK, m)
	})

	// 表单
	ginServer.POST("/user/add", func(context *gin.Context) {
		username := context.PostForm("username")
		password := context.PostForm("password")

		// 加判断逻辑代码

		context.JSON(http.StatusOK, gin.H{
			"msg":      "ok",
			"username": username,
			"password": password,
		})
	})

	// 路由
	ginServer.GET("/test", func(context *gin.Context) {
		// 重定向    StatusMovedPermanently 301
		context.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
	})

	// 404 NoRoute
	ginServer.NoRoute(func(context *gin.Context) {
		context.HTML(http.StatusNotFound, "404.html", nil)
	})

	// 路由组
	userGroup := ginServer.Group("/user")
	{
		userGroup.GET("/add")    // /user/add
		userGroup.GET("/login")  // /user/add
		userGroup.GET("/logout") // /user/add
	}

	orderGroup := ginServer.Group("/order")
	{
		orderGroup.GET("add")
		orderGroup.GET("delte")

	}

	// 服务器端口
	err := ginServer.Run(":8082")
	if err != nil {
		return
	}
}
复制代码
// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Set("name", "wxy") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
		// 调用该请求的剩余处理程序
		c.Next()
		// 不调用该请求的剩余处理程序
		// c.Abort()
		// 计算耗时
		cost := time.Since(start)
		log.Println(cost)
	}
}

多个中间件

复制代码
func m1(c *gin.Context) {
  fmt.Println("m1 ...in")
}
func m2(c *gin.Context) {
  fmt.Println("m2 ...in")
}

func main() {
  router := gin.Default()

  router.GET("/", m1, func(c *gin.Context) {
    fmt.Println("index ...")
    c.JSON(200, gin.H{"msg": "响应数据"})
  }, m2)

  router.Run(":8080")
}

中间件拦截响应c.Abort()

复制代码
package main

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

func m1(c *gin.Context) {
    fmt.Println("m1 ...in")
    c.JSON(200, gin.H{"msg": "第一个中间件拦截了"})
    c.Abort()
}
func m2(c *gin.Context) {
    fmt.Println("m2 ...in")
}

func main() {
    router := gin.Default()

    router.GET("/", m1, func(c *gin.Context) {
        fmt.Println("index ...")
        c.JSON(200, gin.H{"msg": "响应数据"})
    }, m2)

    router.Run(":8080")
}

中间件放行c.Next()

复制代码
package main

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

func m1(c *gin.Context) {
  fmt.Println("m1 ...in")
  c.Next()
  fmt.Println("m1 ...out")
}
func m2(c *gin.Context) {
  fmt.Println("m2 ...in")
  c.Next()
  fmt.Println("m2 ...out")
}

func main() {
  router := gin.Default()

  router.GET("/", m1, func(c *gin.Context) {
    fmt.Println("index ...in")
    c.JSON(200, gin.H{"msg": "响应数据"})
    c.Next()
    fmt.Println("index ...out")
  }, m2)

  router.Run(":8080")
}

文件上传

单个文件上传

文件上传前端页面代码:

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>上传文件示例</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="f1">
    <input type="submit" value="上传">
</form>
</body>
</html>

后端gin框架部分代码:

复制代码
func main() {
	router := gin.Default()
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// 单个文件
		file, err := c.FormFile("f1")
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"message": err.Error(),
			})
			return
		}

		log.Println(file.Filename)
		dst := fmt.Sprintf("D:/go_file/%s", file.Filename)
		// 上传文件到指定的目录
		c.SaveUploadedFile(file, dst)
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("'%s' uploaded!", file.Filename),
		})
	})
    router.Run(":8082")
}

copy

复制代码
file, _ := c.FormFile("file")
log.Println(file.Filename)
// 读取文件中的数据,返回文件对象
fileRead, _ := file.Open()
dst := "./" + file.Filename
// 创建一个文件
out, err := os.Create(dst)
if err != nil {
  fmt.Println(err)
}
defer out.Close()
// 拷贝文件对象到out中
io.Copy(out, fileRead)

读取上传文件

复制代码
file, _ := c.FormFile("file")
// 读取文件中的数据,返回文件对象
fileRead, _ := file.Open()
data, _ := io.ReadAll(fileRead)
fmt.Println(string(data))

多个文件上传

复制代码
func main() {
	router := gin.Default()
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// Multipart form
		form, _ := c.MultipartForm()
		files := form.File["file"]

		for index, file := range files {
			log.Println(file.Filename)
			dst := fmt.Sprintf("./upload/%s_%d", file.Filename, index)
			// 上传文件到指定的目录
			c.SaveUploadedFile(file, dst)
		}
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("%d files uploaded!", len(files)),
		})
	})
    router.Run(":8082")
}

文件下载

复制代码
c.Header("Content-Type", "application/octet-stream")              // 表示是文件流,唤起浏览器下载,一般设置了这个,就要设置文件名
c.Header("Content-Disposition", "attachment; filename="+"牛逼.png") // 用来指定下载下来的文件名
c.Header("Content-Transfer-Encoding", "binary")                   // 表示传输过程中的编码形式,乱码问题可能就是因为它
c.File("uploads/12.png")
/*文件下载浏览器可能会有缓存,这个要注意一下
解决办法就是加查询参数*/

前后端模式下的文件下载

复制代码
c.Header("fileName", "xxx.png")
c.Header("msg", "文件下载成功")
c.File("uploads/12.png")
复制代码
async downloadFile(row) {
this.$http({
   method: 'post',
   url: 'file/upload',
   data:postData,
   responseType: "blob"
}).then(res => {
   const _res = res.data
   let blob = new Blob([_res], {
         type: 'application/png'
       });
   let downloadElement = document.createElement("a");
   let href = window.URL.createObjectURL(blob); //创建下载的链接
   downloadElement.href = href;
   downloadElement.download = res.headers["fileName"]; //下载后文件名
   document.body.appendChild(downloadElement);
   downloadElement.click(); //点击下载
   document.body.removeChild(downloadElement); //下载完成移除元素
   window.URL.revokeObjectURL(href); //释放掉blob对象
 })}s

异常捕获

复制代码
defer func(){
    if err:=recover(); err != nil{
        fmt.Println("捕获异常:", err)
    }
}() // 因为recover只有在发生panic时才会返回一个非nil的值。如果没有panic发生,recover会返回nil

打印日志

复制代码
// pkg/util/logger.go
package util

import (
	"log"
	"os"
	"path"
	"time"

	"github.com/sirupsen/logrus"
)

var LogrusObj *logrus.Logger

func init() {
	// init() 特殊函数 在包被导入时自动执行
	src, _ := setOutPutFile()
	if LogrusObj != nil {
		LogrusObj.Out = src
		return
	}
	// 实例化
	logger := logrus.New()
	logger.Out = src                   // 设置输出
	logger.SetLevel(logrus.DebugLevel) // 设置日志规则
	logger.SetFormatter(&logrus.TextFormatter{
		TimestampFormat: "2006-01-02 15:04:05",
	})
	LogrusObj = logger
}

func setOutPutFile() (*os.File, error) {
	now := time.Now()
	logFilePath := ""
	if dir, err := os.Getwd(); err == nil {
		// os.Getwd()获取当前的工作目录
		logFilePath = dir + "/logs/"
	}
	_, err := os.Stat(logFilePath)
	if os.IsNotExist(err) {
		if err = os.MkdirAll(logFilePath, 0777); err != nil {
			log.Println(err.Error())
			return nil, err
		}
	}
	logFileName := now.Format("2006-01-02") + ".log"
	// 日志文件
	fileName := path.Join(logFilePath, logFileName)
	_, err = os.Stat(fileName)
	if os.IsNotExist(err) {
		if err = os.MkdirAll(fileName, 0777); err != nil {
			// os.MkdirAll是用来创建目录的,而不是文件。应该使用os.Create或os.OpenFile来创建文件
			log.Println(err.Error())
			return nil, err
		}
	}
	// 写入文件
	src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
	if err != nil {
		return nil, err
	}
	return src, nil
}
// util.LogrusObj.Infoln(err)

gin自带日志系统

复制代码
package main

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

func main() {
  // 输出到文件
  f, _ := os.Create("gin.log")
  //gin.DefaultWriter = io.MultiWriter(f)
  // 如果需要同时将日志写入文件和控制台,请使用以下代码。
  gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
  router := gin.Default()
  router.GET("/", func(c *gin.Context) {
    c.JSON(200, gin.H{"msg": "/"})
  })
  router.Run()
}
复制代码
// 查看路由
router.Routes()  // 它会返回已注册的路由列表

// 环境切换    如果不想看到这些debug日志,那么我们可以改为release模式
gin.SetMode(gin.ReleaseMode)
router := gin.Default()

// 修改log的显示
package main

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

func LoggerWithFormatter(params gin.LogFormatterParams) string {

  return fmt.Sprintf(
    "[ feng ] %s  | %d | \t %s | %s | %s \t  %s\n",
    params.TimeStamp.Format("2006/01/02 - 15:04:05"),
    params.StatusCode,  // 状态码
    params.ClientIP,  // 客户端ip
    params.Latency,  // 请求耗时
    params.Method,  // 请求方法
    params.Path,  // 路径
  )
}
func main() {
  router := gin.New()
  router.Use(gin.LoggerWithFormatter(LoggerWithFormatter))
  router.Run()
}
// --------------------------------
func LoggerWithFormatter(params gin.LogFormatterParams) string {
  return fmt.Sprintf(
    "[ feng ] %s  | %d | \t %s | %s | %s \t  %s\n",
    params.TimeStamp.Format("2006/01/02 - 15:04:05"),
    params.StatusCode,
    params.ClientIP,
    params.Latency,
    params.Method,
    params.Path,
  )
}
func main() {
  router := gin.New()
  router.Use(
    gin.LoggerWithConfig(
      gin.LoggerConfig{Formatter: LoggerWithFormatter},
    ),
  )
  router.Run()
}
// ------------------
func LoggerWithFormatter(params gin.LogFormatterParams) string {
  var statusColor, methodColor, resetColor string
  statusColor = params.StatusCodeColor()
  methodColor = params.MethodColor()
  resetColor = params.ResetColor()
  return fmt.Sprintf(
    "[ feng ] %s  | %s %d  %s | \t %s | %s | %s %-7s %s \t  %s\n",
    params.TimeStamp.Format("2006/01/02 - 15:04:05"),
    statusColor, params.StatusCode, resetColor,
    params.ClientIP,
    params.Latency,
    methodColor, params.Method, resetColor,
    params.Path,
  )
}

*****Go跨域

  • 跨域问题

  • 出现跨域问题是浏览器行为,是浏览器认为不安全而进行的拦截

    url的协议、域名、端口三者任一一个与当前页面url不同,就是跨域

  • 如何解决?

    • cors: 在响应头加上对应的响应头即可

    • 代理

cors

复制代码
package main

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

func Cors() gin.HandlerFunc {
  return func(c *gin.Context) {
    method := c.Request.Method
    if c.Request.Header.Get("origin") != "" {
      c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名
      c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
      c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
      c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
      c.Header("Access-Control-Allow-Credentials", "true")
    }
    if method == "OPTIONS" {
      c.AbortWithStatus(http.StatusNoContent)
    }
    c.Next()
  }
}

func Index(c *gin.Context) {
  c.JSON(200, gin.H{
    "code": 0,
    "msg":  "成功",
    "data": gin.H{},
  })
  return
}

func main() {
  r := gin.Default()
  r.GET("/api/no_cors", Index)
  r.POST("/api/no_cors", Index)
  r.GET("/api/cors", Cors(), Index)
  r.POST("/api/cors", Cors(), Index)
  r.Run(":8080")
}

代理

这种方案是目前最主流的跨域解决方案,它分为两类,一个是开发环境,一个是生产环境

开发环境解决跨域

vue3为例,vite提供了代理功能

复制代码
import {fileURLToPath, URL} from 'node:url'

import {defineConfig, loadEnv} from 'vite'
import vue from '@vitejs/plugin-vue'
import type {ImportMetaEnv} from "./env";
// https://vitejs.dev/config/
export default defineConfig(({mode}) => {
    let env: Record<keyof ImportMetaEnv, string> = loadEnv(mode, process.cwd())

    const serverUrl =  env.VITE_SERVER_URL
    const wsUrl = serverUrl.replace("http", "ws")
    return {
        plugins: [
            vue(),
        ],
        envDir: "./",
        resolve: {
            alias: {
                '@': fileURLToPath(new URL('./src', import.meta.url))
            }
        },
        server: {
            host: "0.0.0.0",
            port: 80,
            proxy: {
                "/api": {
                    target: serverUrl,
                    changeOrigin: true,
                }
            }
        }
    }
})

凡是使用代理的情况,axios请求的后端路径就不能写死了

因为一旦写死了,代理就捕获不到了,相当于还是前端直接请求后端接口,肯定会跨域的

生产环境解决跨域

使用nginx的反向代理

复制代码
server {
    listen       80;
    server_name  blog.fengfengzhidao.com;

    location / {
      try_files $uri $uri/ /index.html;  
      root   /opt/gvb/web/dist;
      index  index.html index.htm;
    }

    location /api/ {
      # rewrite ^/(api/.*) /$1 break;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header REMOTE-HOST $remote_addr;
      proxy_pass http://127.0.0.1:8082/api/;
    }
    location /uploads/ {
      alias /opt/gvb/server/uploads/;
    }

    access_log  /opt/gvb/access.log;
    error_log   /opt/gvb/error.log;
}

* http反向代理(网关)

复制代码
package main
import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
)
type Proxy struct {}
func (Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	remote, _ := url.Parse("http://127.0.0.1:20023")  // 要转到访问的地址
	reverseProxy := httputil.NewSingleHostReverseProxy(remote)
	reverseProxy.ServeHTTP(w, r)
}
func main() {
	addr := "127.0.0.1:9090"  // 监听地址
	fmt.Println("gateway runserver on %s\n", addr)
	http.ListenAndServe(addr, Proxy{})
}

<!-- -->

Gin

复制代码
// cmd
go get -u github.com/gin-gonic/gin

项目开始

路由组封装

复制代码
// main.go
package main

import "godemo/router"

func main() {
	r := router.Router()
	r.Run(":9090")
}
复制代码
// router/routers.go
package router

import (
	"net/http"

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

func Router() *gin.Engine {
	r := gin.Default()

	r.GET("/hello", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "Hello World")
	})

	user := r.Group("/user")
	{
		user.POST("/list", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user list")
		})
		user.PUT("/add", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user add")
		})
		user.DELETE("/delete", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user delete")
		})
	}

	return r
}

封装JS

复制代码
// controllers/common.go
package controllers

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

type JsonStruct struct {
	Code  int         `json:"code"`
	Msg   interface{} `json:"msg"`
	Data  interface{} `json:"data"`
	Count int64       `json:"count"`
}

func ReturnSuccess(c *gin.Context, code int, msg interface{}, data interface{}, count int64) {
	json := &JsonStruct{
		Code:  code,
		Msg:   msg,
		Data:  data,
		Count: count,
	}
	c.JSON(200, json)
}

func ReturnErrer(c *gin.Context, code int, msg interface{}) {
	json := &JsonStruct{
		Code: code,
		Msg:  msg,
	}
	c.JSON(200, json)
}
复制代码
// router/routers.go
package router

import (
	"godemo/controllers"
	"net/http"

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

func Router() *gin.Engine {
	r := gin.Default()

	r.GET("/hello", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "Hello World")
	})

	user := r.Group("/user")
	{
		user.GET("/info", controllers.GetUserInfo)

		user.POST("/list", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user list")
		})
		user.PUT("/add", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user add")
		})
		user.DELETE("/delete", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user delete")
		})
	}
	return r
}
复制代码
// controllers/user.go
package controllers

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

func GetUserInfo(c *gin.Context) {
	ReturnSuccess(c, 0, "success", "user info", 1)
}
复制代码
// router/routers.go
package router

import (
	"godemo/controllers"
	"net/http"

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

func Router() *gin.Engine {
	r := gin.Default()

	r.GET("/hello", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "Hello World")
	})

	user := r.Group("/user")
	{
		user.GET("/info", controllers.GetUserInfo)

		user.POST("/list", controllers.GetList)

		user.PUT("/add", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user add")
		})
		user.DELETE("/delete", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user delete")
		})
	}

	return r
}
复制代码
// controllers/user.go
package controllers

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

func GetUserInfo(c *gin.Context) {
	ReturnSuccess(c, 0, "success", "user info", 1)
}

func GetList(c *gin.Context) {
	ReturnErrer(c, 4004, "没有相关信息")
}
复制代码
// controllers/common.go
package controllers

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

type JsonStruct struct {
	Code  int         `json:"code"`
	Msg   interface{} `json:"msg"`
	Data  interface{} `json:"data"`
	Count int64       `json:"count"`
}

type JsonErrStruct struct {
	Code int         `json:"code"`
	Msg  interface{} `json:"msg"`
}

func ReturnSuccess(c *gin.Context, code int, msg interface{}, data interface{}, count int64) {
	json := &JsonStruct{
		Code:  code,
		Msg:   msg,
		Data:  data,
		Count: count,
	}
	c.JSON(200, json)
}

func ReturnErrer(c *gin.Context, code int, msg interface{}) {
	json := &JsonErrStruct{
		Code: code,
		Msg:  msg,
	}
	c.JSON(200, json)
}

结构体优化

  • controllers/user.go中的函数,因为都在一个包里,所以当新建的order.go中出现该函数就会报错,这时就可以用结构体方法进行优化
复制代码
// controllers/order.go
func GetList(c *gin.Context) {
	ReturnErrer(c, 4004, "没有相关信息")
}
复制代码
// controllers/user.go
package controllers

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

type UserController struct{}

func (u UserController)GetUserInfo(c *gin.Context) {
	ReturnSuccess(c, 0, "success", "user info", 1)
}

func (u UserController)GetList(c *gin.Context) {
	ReturnErrer(c, 4004, "没有相关信息list")
}
复制代码
// controllers/order.go
package controllers

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

type OrderContreller struct{}

func (o OrderContreller) GetList(c *gin.Context) {
	ReturnErrer(c, 4004, "没有相关信息")
}
复制代码
// router/routers.go
package router

import (
	"godemo/controllers"
	"net/http"

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

func Router() *gin.Engine {
	r := gin.Default()

	r.GET("/hello", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "Hello World")
	})

	user := r.Group("/user")
	{
		user.GET("/info", controllers.UserController{}.GetUserInfo)

		user.POST("/list", controllers.UserController{}.GetList)

		user.PUT("/add", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user add")
		})
		user.DELETE("/delete", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user delete")
		})
	}

	order := r.Group("/order")
	{
		order.GET("list", controllers.OrderContreller{}.GetList)
	}

	return r
}

获取请求参数

方式一Param

复制代码
// router/routers.go
package router

import (
	"godemo/controllers"
	"net/http"

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

func Router() *gin.Engine {
	r := gin.Default()

	r.GET("/hello", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "Hello World")
	})

	user := r.Group("/user")
	{
		user.GET("/info/:id/:name", controllers.UserController{}.GetUserInfo)

		user.POST("/list", controllers.UserController{}.GetList)

		user.PUT("/add", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user add")
		})

		user.DELETE("/delete", func(ctx *gin.Context) {
			ctx.String(http.StatusOK, "user delete")
		})
	}

	order := r.Group("/order")
	{
		order.GET("list", controllers.OrderContreller{}.GetList)
	}

	return r
}
复制代码
// controllers/user.go
package controllers

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

type UserController struct{}

func (u UserController) GetUserInfo(c *gin.Context) {
	id := c.Param("id")
	name := c.Param("name")
	ReturnSuccess(c, 0, name, id, 1)
}

func (u UserController) GetList(c *gin.Context) {
	ReturnErrer(c, 4004, "没有相关信息list")
}

方式二获取POST的参数 PostForm

复制代码
// controllers/order.go
package controllers

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

type OrderController struct{}

func (o OrderController) GetList(c *gin.Context) {
	cid := c.PostForm("cid")
	name := c.DefaultPostForm("name", "xiaohua")
	ReturnSuccess(c, 0, name, cid, 1)
}

方式三获取JSON参数 BindJSON_ Map&&结构体

复制代码
// controllers/order.go
package controllers

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

type OrderController struct{}

func (o OrderController) GetList(c *gin.Context) {
	param := make(map[string]interface{})
	err := c.BindJSON(&param)
	if err == nil {
		ReturnSuccess(c, 0, param["name"], param["cid"], 1)
		return
	}
	ReturnErrer(c, 4001, gin.H{"err": err})
}
复制代码
// controllers/order.go
package controllers

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

type OrderController struct{}

type Search struct {
	Name string `json:"name"`
	Cid  int    `json:"cid"`
}

func (o OrderController) GetList(c *gin.Context) {
	search := &Search{}
	err := c.BindJSON(&search)
	if err == nil {
		ReturnSuccess(c, 0, search.Name, search.Cid, 1)
		return
	}
	ReturnErrer(c, 4001, gin.H{"err": err})
}

异常捕获

复制代码
// controllers/user.go
package controllers

import (
	"fmt"

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

type UserController struct{}

func (u UserController) GetUserInfo(c *gin.Context) {
	id := c.Param("id")
	name := c.Param("name")
	ReturnSuccess(c, 0, name, id, 1)
}

func (u UserController) GetList(c *gin.Context) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("捕获异常:", err)
		}
	}()
    // 因为recover只有在发生panic时才会返回一个非nil的值。如果没有panic发生,recover会返回nil
	num1 := 1
	num2 := 0
	num3 := num1 / num2
	ReturnErrer(c, 4004, num3)
}
// 异常错误输出到日志中

日志收集

复制代码
// pkg/util/logger.go
package util

import (
	"log"
	"os"
	"path"
	"time"

	"github.com/sirupsen/logrus"
)

var LogrusObj *logrus.Logger

func init() {
	// init() 特殊函数 在包被导入时自动执行
	src, _ := setOutPutFile()
	if LogrusObj != nil {
		LogrusObj.Out = src
		return
	}
	// 实例化
	logger := logrus.New()
	logger.Out = src                   // 设置输出
	logger.SetLevel(logrus.DebugLevel) // 设置日志规则
	logger.SetFormatter(&logrus.TextFormatter{
		TimestampFormat: "2006-01-02 15:04:05",
	})
	LogrusObj = logger
}

func setOutPutFile() (*os.File, error) {
	now := time.Now()
	logFilePath := ""
	if dir, err := os.Getwd(); err == nil {
		// os.Getwd()获取当前的工作目录
		logFilePath = dir + "/logs/"
	}
	_, err := os.Stat(logFilePath)
	if os.IsNotExist(err) {
		if err = os.MkdirAll(logFilePath, 0777); err != nil {
			log.Println(err.Error())
			return nil, err
		}
	}
	logFileName := now.Format("2006-01-02") + ".log"
	// 日志文件
	fileName := path.Join(logFilePath, logFileName)
	_, err = os.Stat(fileName)
	if os.IsNotExist(err) {
		if err = os.MkdirAll(fileName, 0777); err != nil {
			// os.MkdirAll是用来创建目录的,而不是文件。应该使用os.Create或os.OpenFile来创建文件
			log.Println(err.Error())
			return nil, err
		}
	}
	// 写入文件
	src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
	if err != nil {
		return nil, err
	}
	return src, nil
}
// util.LogrusObj.Infoln(err)
复制代码
// controllers/user.go
package controllers

import (
	"fmt"
	pkg "godemo/pkg/logger"

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

type UserController struct{}

func (u UserController) GetUserInfo(c *gin.Context) {
	id := c.Param("id")
	name := c.Param("name")
	ReturnSuccess(c, 0, name, id, 1)
}

func (u UserController) GetList(c *gin.Context) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("捕获异常:", err)
			pkg.LogrusObj.Infoln(err)
		}
	}()
	num1 := 1
	num2 := 0
	num3 := num1 / num2
	ReturnErrer(c, 4004, num3)
}

引入Gorm框架

复制代码
go get -u gorm.io/driver/mysql
go get -u github.com/jinzhu/gorm
net start mysql
mysql -u root -p
net stop mysql
复制代码
// config/db.go
package config

const (
	Mysqldb = "root:l20030328@tcp(127.0.0.1:3306)/ranking?charset=utf8"
)
复制代码
// dao/dao.go
package dao

import (
	"godemo/config"
	pkg "godemo/pkg/logger"
	"time"

	"github.com/jinzhu/gorm"
    _ "gorm.io/driver/mysql"
)

var (
	Db  *gorm.DB
	err error
)

func init() {
	Db, err = gorm.Open("mysql", config.Mysqldb)
	if err != nil {
		pkg.LogrusObj.Error(map[string]interface{}{"mysql conent error": err})
	}
	if Db.Error != nil {
		pkg.LogrusObj.Error(map[string]interface{}{"datebase conent error": Db.Error})
	}
	Db.DB().SetMaxIdleConns(10)
	Db.DB().SetMaxOpenConns(100)
	Db.DB().SetConnMaxLifetime(time.Hour)
}
复制代码
// models/user.go
package models

import "godemo/dao"

type User struct {
	Id   int
	name string
}

func (User) TableName() string {
	return "user"
}

func GetUserTest(id int) (User, error) {
	var user User
	err := dao.Db.Where("id = ?", id).First(&user).Error
	return user, err
}
复制代码
// controllers/user.go
package controllers

import (
	"fmt"
	"godemo/models"
	"strconv"

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

type UserController struct{}

func (u UserController) GetUserInfo(c *gin.Context) {
	idStr := c.Param("id")
	name := c.Param("name")
	id, _ := strconv.Atoi(idStr)

	user, _ := models.GetUserTest(id)

	ReturnSuccess(c, 0, name, user, 1)
}

func (u UserController) GetList(c *gin.Context) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("捕获异常:", err)
		}
	}()
	num1 := 1
	num2 := 0
	num3 := num1 / num2
	ReturnErrer(c, 4004, num3)
}

数据库crud的实现

复制代码
// models/user.go
package models

import "godemo/dao"

type User struct {
	Id       int
	Username string
}

func (User) TableName() string {
	return "user"
}

func init() {
	dao.Db.AutoMigrate(&User{})
}

func GetUserTest(id int) (User, error) {
	var user User
	err := dao.Db.Where("id = ?", id).First(&user).Error
	return user, err
}

func GetUserListTest() ([]User, error) {
	var users []User
	err := dao.Db.Where("id < ?", 3).Find(&users).Error
	return users, err
}

func AddUser(username string) (int, error) {
	user := User{Username: username}
	err := dao.Db.Create(&user).Error
	return user.Id, err
}

func UpdateUser(id int, username string) {
	dao.Db.Model(&User{}).Where("id = ?", id).Update("username", username)
}

func DeleteUser(id int) error {
	err := dao.Db.Delete(&User{}, id).Error
	return err
}
复制代码
// controllers/user.go
package controllers

import (
	"fmt"
	"godemo/models"
	"strconv"

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

type UserController struct{}

func (u UserController) GetUserInfo(c *gin.Context) {
	idStr := c.Param("id")
	// name := c.Param("name")
	id, _ := strconv.Atoi(idStr)

	user, _ := models.GetUserTest(id)

	ReturnSuccess(c, 0, "name", user, 1)
}

func (u UserController) AddUser(c *gin.Context) {
	username := c.DefaultPostForm("username", "")
	id, err := models.AddUser(username)
	if err != nil {
		ReturnErrer(c, 4002, "保存错误")
		return
	}
	ReturnSuccess(c, 0, "保存成功", id, 1)
}

func (u UserController) UpdateUser(c *gin.Context) {
	username := c.DefaultPostForm("username", "")
	idStr := c.DefaultPostForm("id", "")
	id, _ := strconv.Atoi(idStr)
	models.UpdateUser(id, username)
	ReturnSuccess(c, 0, "更新成功", true, 1)
}

func (u UserController) DeleteUser(c *gin.Context) {
	idStr := c.DefaultPostForm("id", "")
	id, _ := strconv.Atoi(idStr)
	err := models.DeleteUser(id)
	if err != nil {
		ReturnErrer(c, 4003, "删除错误")
		return
	}
	ReturnSuccess(c, 0, "删除成功", true, 1)
}

func (u UserController) GetList(c *gin.Context) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("捕获异常:", err)
		}
	}()
	num1 := 1
	num2 := 0
	num3 := num1 / num2
	ReturnErrer(c, 4004, num3)
}

func (u UserController) GetUserListTest(c *gin.Context) {
	users, err := models.GetUserListTest()
	if err != nil {
		ReturnErrer(c, 4004, "没有相关数据")
		return
	}
	ReturnSuccess(c, 0, "查询成功", users, 1)
}
复制代码
// router/routers.go
package router

import (
	"godemo/controllers"
	"net/http"

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

func Router() *gin.Engine {
	r := gin.Default()

	r.GET("/hello", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "Hello World")
	})

	user := r.Group("/user")
	{
		user.GET("/info/:id", controllers.UserController{}.GetUserInfo)

		user.POST("/list", controllers.UserController{}.GetList)

		user.POST("/add", controllers.UserController{}.AddUser)
		user.POST("/update", controllers.UserController{}.UpdateUser)

		user.POST("/delete", controllers.UserController{}.DeleteUser)
		user.POST("/list/test", controllers.UserController{}.GetUserListTest)
	}

	order := r.Group("/order")
	{
		order.GET("list", controllers.OrderController{}.GetList)
	}

	return r
}

用户注册登录,以及会话的使用

复制代码
// controllers/user.go
package controllers

type UserController struct{}
复制代码
// models/user.go
package models

import "godemo/dao"

type User struct {
	Id       int
	Username string
}

func (User) TableName() string {
	return "user"
}

func init() {
	dao.Db.AutoMigrate(&User{})
}
复制代码
// router/routers.go
package router

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

func Router() *gin.Engine {
	r := gin.Default()

	user := r.Group("/user")
	{

	}

	return r
}

注册

复制代码
// contrillers/common.go
package controllers

import (
	"crypto/md5"
	"encoding/hex"

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

type JsonStruct struct {
	Code  int         `json:"code"`
	Msg   interface{} `json:"msg"`
	Data  interface{} `json:"data"`
	Count int64       `json:"count"`
}

type JsonErrStruct struct {
	Code int         `json:"code"`
	Msg  interface{} `json:"msg"`
}

func ReturnSuccess(c *gin.Context, code int, msg interface{}, data interface{}, count int64) {
	json := &JsonStruct{
		Code:  code,
		Msg:   msg,
		Data:  data,
		Count: count,
	}
	c.JSON(200, json)
}

func ReturnErrer(c *gin.Context, code int, msg interface{}) {
	json := &JsonErrStruct{
		Code: code,
		Msg:  msg,
	}
	c.JSON(200, json)
}

// md5加密
func EncryMd5(s string) string {
	ctx := md5.New()
	ctx.Write([]byte(s))
	return hex.EncodeToString(ctx.Sum(nil))
}
复制代码
// controllers/user.go
package controllers

import (
	"godemo/models"

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

type UserController struct{}

func (u UserController) Register(c *gin.Context) {
	// 接收用户名,密码,确认密码
	username := c.DefaultPostForm("username", "")
	password := c.DefaultPostForm("password", "")
	confirmPassword := c.DefaultPostForm("confirmPassword", "")
	if username == "" || password == "" || confirmPassword == "" {
		ReturnErrer(c, 4001, "请输入正确信息")
		return
	}
	if password != confirmPassword {
		ReturnErrer(c, 4001, "密码和确认密码不一致")
		return
	}
	user, _ := models.GetUserInfoByUsername(username)
	if user.Id != 0 {
		ReturnErrer(c, 4001, "用户名已存在")
		return
	}
	_, err := models.AddUser(username, EncryMd5(password))
	if err != nil {
		ReturnErrer(c, 4001, "保存失败,请联系管理员")
		return
	}
	ReturnSuccess(c, 1, "注册成功", user.Id, 1)
}
复制代码
// models/user.go
package models

import (
	"godemo/dao"
	"time"
)

type User struct {
	Id         int    `json:"id"`
	Username   string `json:"username"`
	Password   string `json:"password"`
	AddTime    int64  `json:"addTime"`
	UpdateTime int64  `json:"updateTime"`
}

func (User) TableName() string {
	return "user"
}

func init() {
	dao.Db.AutoMigrate(&User{})
}

// 判断用户名是否存在
func GetUserInfoByUsername(username string) (User, error) {
	var user User
	err := dao.Db.Where("username = ?", username).First(&user).Error
	return user, err
}

// 创建用户
func AddUser(username string, password string) (int, error) {
	user := User{Username: username, Password: password,
		AddTime: time.Now().Unix(), UpdateTime: time.Now().Unix(),
	}
	err := dao.Db.Create(&user).Error
	return user.Id, err
}
复制代码
// router/routers.go
package router

import (
	"godemo/controllers"

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

func Router() *gin.Engine {
	r := gin.Default()

	user := r.Group("/user")
	{
		user.POST("/register", controllers.UserController{}.Register)
	}

	return r
}

登录|会话签发

复制代码
redis-server.exe  --service-start
net start mysql
net stop mysql
redis-server.exe  --service-stop
复制代码
go get github.com/gin-contrib/sessions
go get github.com/gin-contrib/sessions/redis
复制代码
// router/routers.go
package router

import (
	"godemo/config"
	"godemo/controllers"

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

func Router() *gin.Engine {
	r := gin.Default()

	store, _ := sessions_redis.NewStore(10, "tcp", config.RedisAddress, "", []byte("secret"))
	// []byte("secret") 是用于加密会话数据的密钥
	r.Use(sessions.Sessions("mysession", store))
	// 将会话中间件添加到路由,检查是否存在名为 "mysession" 的会话,并在不存在时创建一个

	user := r.Group("/user")
	{
		user.POST("/register", controllers.UserController{}.Register)
		user.POST("/login", controllers.UserController{}.Login)
	}

	return r
}
复制代码
// config/redis.go
package config

const (
	RedisAddress = "localhost:6379"
)
复制代码
// controllers/user.go
package controllers

import (
	"godemo/models"
	"strconv"

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

type UserController struct{}

// 注册
func (u UserController) Register(c *gin.Context) {
	// 接收用户名,密码,确认密码
	username := c.DefaultPostForm("username", "")
	password := c.DefaultPostForm("password", "")
	confirmPassword := c.DefaultPostForm("confirmPassword", "")
	if username == "" || password == "" || confirmPassword == "" {
		ReturnErrer(c, 4001, "请输入正确信息")
		return
	}
	if password != confirmPassword {
		ReturnErrer(c, 4001, "密码和确认密码不一致")
		return
	}
	user, _ := models.GetUserInfoByUsername(username)
	if user.Id != 0 {
		ReturnErrer(c, 4001, "用户名已存在")
		return
	}
	_, err := models.AddUser(username, EncryMd5(password))
	if err != nil {
		ReturnErrer(c, 4001, "保存失败,请联系管理员")
		return
	}
	ReturnSuccess(c, 1, "注册成功", user.Id, 1)
}

type UserApi struct {
	Id       int    `json:"id"`
	Username string `json:"username"`
}

// 登录
func (u UserController) Login(c *gin.Context) {
	// 接受用户名和密码
	username := c.DefaultPostForm("username", "")
	password := c.DefaultPostForm("password", "")
	if username == "" || password == "" {
		ReturnErrer(c, 4001, "请输入正确的信息")
		return
	}

	user, _ := models.GetUserInfoByUsername(username)
	if user.Id == 0 {
		ReturnErrer(c, 4004, "用户名或密码不正确")
		return
	}
	if user.Password != EncryMd5(password) {
		ReturnErrer(c, 4004, "用户名或密码不正确")
		return
	}
	session := sessions.Default(c)
	// 从请求上下文中获取默认会话
	session.Set("login:"+strconv.Itoa(user.Id), user.Id)
	// 将会话键设置为 "login:" 后跟用户的 ID
	session.Save()
	// 保存会话数据,将数据发送到 Redis 服务器进行存储
	data := UserApi{Id: user.Id, Username: username}
	ReturnSuccess(c, 0, "登录成功", data, 1)
}
复制代码
// router/routers.go
package router

import (
	"godemo/config"
	"godemo/controllers"

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

func Router() *gin.Engine {
	r := gin.Default()

	store, _ := sessions_redis.NewStore(10, "tcp", config.RedisAddress, "", []byte("secret"))
	r.Use(sessions.Sessions("mysession", store))

	user := r.Group("/user")
	{
		user.POST("/register", controllers.UserController{}.Register)
		user.POST("/login", controllers.UserController{}.Login)
	}

	return r
}

另一种的token签发

复制代码
package routes

import (
	"todo_list/api"
	"todo_list/middieware"

	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
)

func NewRouter() *gin.Engine {
	r := gin.Default() // 创建gin引擎
	store := cookie.NewStore([]byte("something-very-secret"))
	//  初始化cookie会话存储
	r.Use(sessions.Sessions("mysession", store))
	//  设置会话中间件
	v1 := r.Group("api/v1") // 定义一个路由组v1
	{
		// 用户操作,在路由组内定义路由
		v1.POST("user/register", api.UserRegister)
		v1.POST("user/login", api.UserLogin)
		authed := v1.Group("/")
		authed.Use(middieware.JWT())
		// 运行时先验证middieware.JWT()这个中间件看有没有这个权限
		{
			authed.POST("task", api.CreateTask)
		}
	}
	return r
}
复制代码
// service/user.go
	// 密码验证成功后发一个token,为了其他功能需要身份验证所给前端存储的
	// 创建一个备忘录,这个功能就要token,不然不知道是谁创建的备忘录
	token, err := utils.GenerateToken(user.ID, service.UserName, service.Password)
	if err != nil {
		return serializer.Response{
			Status: 500,
			Msg:    "Token签发错误",
		}
	}
	return serializer.Response{
		Status: 200,
		Data:   serializer.TokenData{User: serializer.BuildUser(user), Token: token},
		Msg:    "登录成功",
	}
}
复制代码
package middieware

import (
	"time"
	"todo_list/pkg/utils"

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

func JWT() gin.HandlerFunc {
	return func(c *gin.Context) {
		code := 200
		token := c.GetHeader("Authorization") // 从HTTP请求的头部获取名为"Authorization"的值,这通常是JWT存放的地方
		if token == "" {
			code = 404
		} else {
			claim, err := utils.ParseToken(token)
			if err != nil {
				code = 403 // 无权限,token是无权的,是假的
			} else if time.Now().Unix() > claim.ExpiresAt {
				code = 401 // Token无效
				// JWT解析成功,但当前时间已经超过了claim.ExpiresAt(即token已过期)
			}
		}
		if code != 200 {
			c.JSON(200, gin.H{
				// map[string]interface{}的缩写
				"status": code,
				"msg":    "Token解析错误",
			})
			c.Abort() // 终止当前的请求处理流程
			return
		}
		c.Next() // 将请求传递给后续的中间件或路由处理函数
	}
}
复制代码
package utils

import (
	"time"

	"github.com/dgrijalva/jwt-go"
)

var JwtSecret = []byte("ABAB")

type Claims struct {
	Id       uint   `json:"id"`
	UserName string `json:"user_name"`
	Password string `json:"password"`
	jwt.StandardClaims
}

// 签发token
func GenerateToken(id uint, username, password string) (string, error) {
	notTime := time.Now()
	expireTime := notTime.Add(24 * time.Hour)
	Claims := Claims{
		Id:       id,
		UserName: username,
		Password: password,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expireTime.Unix(),
			Issuer:    "todo_list",
		},
	}
	tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, Claims)
	token, err := tokenClaims.SignedString(JwtSecret)
	return token, err

}

// 验证token
func ParseToken(token string) (*Claims, error) {
	tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(Token *jwt.Token) (interface{}, error) {
		return JwtSecret, nil
	})
	if tokenClaims != nil {
		if Claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
			return Claims, nil
		}
	}
	return nil, err
}

投票功能

复制代码
redis-server.exe  --service-start
net start mysql
net stop mysql
redis-server.exe  --service-stop

查看player列表

复制代码
// controllers/player.go
// controllers/player.go
package controllers

import (
	"godemo/models"
	"strconv"

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

type PlayerController struct{}

func (p PlayerController) GetPlayers(c *gin.Context) {
	aidStr := c.DefaultPostForm("aid", "0")
	aid, _ := strconv.Atoi(aidStr)

	rs, err := models.GetPlayers(aid)
	if err != nil {
		ReturnErrer(c, 4004, "没有相关信息")
		return
	}
	ReturnSuccess(c, 0, "success", rs, 1)
}
复制代码
// models/player.go
package models

import "godemo/dao"

type Player struct {
	Id          int    `json:"id"`
	Aid         int    `json:"aid"`
	Ref         string `json:"ref"`
	Nickname    string `json:"nickname"`
	Declaration string `json:"declaration"`
	Avatar      string `json:"avatar"`
	Score       int    `json:"score"`
}

func (Player) TableName() string {
	return "player"
}

func init() {
	dao.Db.AutoMigrate(&Player{})
}

func GetPlayers(aid int) ([]Player, error) {
	var players []Player
	err := dao.Db.Where("aid = ?", aid).Find(&players).Error
	return players, err
}
复制代码
// router/routers.go
package router

import (
	"godemo/config"
	"godemo/controllers"

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

func Router() *gin.Engine {
	r := gin.Default()

	store, _ := sessions_redis.NewStore(10, "tcp", config.RedisAddress, "", []byte("secret"))
	// []byte("secret") 是用于加密会话数据的密钥
	r.Use(sessions.Sessions("mysession", store))
	// 将会话中间件添加到路由,检查是否存在名为 "mysession" 的会话,并在不存在时创建一个

	user := r.Group("/user")
	{
		user.POST("/register", controllers.UserController{}.Register)
		user.POST("/login", controllers.UserController{}.Login)
	}

	player := r.Group("/player")
	{
		player.POST("/list", controllers.PlayerController{}.GetPlayers)
	}

	return r
}

投票实现

复制代码
// models/user.go
package models

import (
	"godemo/dao"
	"time"
)

type User struct {
	Id         int    `json:"id"`
	Username   string `json:"username"`
	Password   string `json:"password"`
	AddTime    int64  `json:"addTime"`
	UpdateTime int64  `json:"updateTime"`
}

func (User) TableName() string {
	return "user"
}

func init() {
	dao.Db.AutoMigrate(&User{})
}

// 判断用户名是否存在
func GetUserInfoByUsername(username string) (User, error) {
	var user User
	err := dao.Db.Where("username = ?", username).First(&user).Error
	return user, err
}

func GetUserInfo(id int) (User, error) {
	var user User
	err := dao.Db.Where("id = ?", id).First(&user).Error
	return user, err
}

// 创建用户
func AddUser(username string, password string) (int, error) {
	user := User{Username: username, Password: password,
		AddTime: time.Now().Unix(), UpdateTime: time.Now().Unix(),
	}
	err := dao.Db.Create(&user).Error
	return user.Id, err
}
复制代码
// controllers/vote.go
// controllers/vote.go
package controllers

import (
	"godemo/models"
	"strconv"

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

type VoteController struct{}

func (v VoteController) AddVote(c *gin.Context) {
	userIdStr := c.DefaultPostForm("userId", "0")
	playerIdStr := c.DefaultPostForm("playerId", "0")
	userId, _ := strconv.Atoi(userIdStr)
	playerId, _ := strconv.Atoi(playerIdStr)

	if userId == 0 || playerId == 0 {
		ReturnErrer(c, 4001, "请输入正确的信息")
		return
	}
	user, _ := models.GetUserInfo(userId)
	if user.Id == 0 {
		ReturnErrer(c, 4001, "投票用户不存在")
		return
	}
	player, _ := models.GetPlayerInfo(playerId)
	if player.Id == 0 {
		ReturnErrer(c, 4001, "参赛选手不存在")
		return
	}
	vote, _ := models.GetVoteInfo(userId, playerId)
	if vote.Id != 0 {
		ReturnErrer(c, 4001, "已投票")
		return
	}
	rs, err := models.AddVote(userId, playerId)
	if err == nil {
		// 更新参赛选手分数字段,自增1
		models.UpdatePlayerScore(playerId)
		ReturnSuccess(c, 0, "投票成功", rs, 1)
		return
	}
	ReturnErrer(c, 4004, "请联系管理员")
	return
}
复制代码
// models/player.go
// models/player.go
package models

import (
	"godemo/dao"

	"github.com/jinzhu/gorm"
)

type Player struct {
	Id          int    `json:"id"`
	Aid         int    `json:"aid"`
	Ref         string `json:"ref"`
	Nickname    string `json:"nickname"`
	Declaration string `json:"declaration"`
	Avatar      string `json:"avatar"`
	Score       int    `json:"score"`
}

func (Player) TableName() string {
	return "player"
}

func init() {
	dao.Db.AutoMigrate(&Player{})
}

func GetPlayers(aid int) ([]Player, error) {
	var players []Player
	err := dao.Db.Where("aid = ?", aid).Find(&players).Error
	return players, err
}

func GetPlayerInfo(id int) (Player, error) {
	var player Player
	err := dao.Db.Where("id = ?", id).First(&player).Error
	return player, err
}

func UpdatePlayerScore(id int) {
	var player Player
	dao.Db.Model(&player).Where("id = ?", id).UpdateColumn("score", gorm.Expr("score + ?", 1))
}
复制代码
// models/vote.go
package models

import (
	"godemo/dao"
	"time"
)

type Vote struct {
	Id       int   `json:"id"`
	UserId   int   `json:"userId"`
	PlayerId int   `json:"playerId"`
	AddTime  int64 `json:"addTime"`
}

func (Vote) TableName() string {
	return "vote"
}

func init() {
	dao.Db.AutoMigrate(&Vote{})
}

func GetVoteInfo(userId int, playerId int) (Vote, error) {
	var vote Vote
	err := dao.Db.Where("user_id = ? AND player_id = ?", userId, playerId).First(&vote).Error
	return vote, err
}

func AddVote(userId, playerId int) (int, error) {
	vote := Vote{UserId: userId, PlayerId: playerId, AddTime: time.Now().Unix()}
	err := dao.Db.Create(&vote).Error
	return vote.Id, err
}
复制代码
// router/routers.go
package router

import (
	"godemo/config"
	"godemo/controllers"

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

func Router() *gin.Engine {
	r := gin.Default()

	store, _ := sessions_redis.NewStore(10, "tcp", config.RedisAddress, "", []byte("secret"))
	// []byte("secret") 是用于加密会话数据的密钥
	r.Use(sessions.Sessions("mysession", store))
	// 将会话中间件添加到路由,检查是否存在名为 "mysession" 的会话,并在不存在时创建一个

	user := r.Group("/user")
	{
		user.POST("/register", controllers.UserController{}.Register)
		user.POST("/login", controllers.UserController{}.Login)
	}

	player := r.Group("/player")
	{
		player.POST("/list", controllers.PlayerController{}.GetPlayers)
	}

	vote := r.Group("/vote")
	{
		vote.POST("/add", controllers.VoteController{}.AddVote)
	}

	return r
}

基于Mysql的排序功能

复制代码
// models/player.go
package models

import (
	"godemo/dao"

	"github.com/jinzhu/gorm"
)

type Player struct {
	Id          int    `json:"id"`
	Aid         int    `json:"aid"`
	Ref         string `json:"ref"`
	Nickname    string `json:"nickname"`
	Declaration string `json:"declaration"`
	Avatar      string `json:"avatar"`
	Score       int    `json:"score"`
}

func (Player) TableName() string {
	return "player"
}

func init() {
	dao.Db.AutoMigrate(&Player{})
}

func GetPlayers(aid int, sort string) ([]Player, error) {
	var players []Player
	err := dao.Db.Where("aid = ?", aid).Order(sort).Find(&players).Error
	return players, err
}

func GetPlayerInfo(id int) (Player, error) {
	var player Player
	err := dao.Db.Where("id = ?", id).First(&player).Error
	return player, err
}

func UpdatePlayerScore(id int) {
	var player Player
	dao.Db.Model(&player).Where("id = ?", id).UpdateColumn("score", gorm.Expr("score + ?", 1))
}
复制代码
// controllers/player.go
package controllers

import (
	"godemo/models"
	"strconv"

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

type PlayerController struct{}

func (p PlayerController) GetPlayers(c *gin.Context) {
	aidStr := c.DefaultPostForm("aid", "0")
	aid, _ := strconv.Atoi(aidStr)

	rs, err := models.GetPlayers(aid, "id asc")
	if err != nil {
		ReturnErrer(c, 4004, "没有相关信息")
		return
	}
	ReturnSuccess(c, 0, "success", rs, 1)
}

func (p PlayerController) GetRanking(c *gin.Context) {
	aidStr := c.DefaultPostForm("aid", "0")
	aid, _ := strconv.Atoi(aidStr)
	rs, err := models.GetPlayers(aid, "score desc")
	if err != nil {
		ReturnErrer(c, 4004, "没有相关信息")
		return
	}
	ReturnSuccess(c, 0, "success", rs, 1)
	return
}
复制代码
// router/routers.go
package router

import (
	"godemo/config"
	"godemo/controllers"

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

func Router() *gin.Engine {
	r := gin.Default()

	store, _ := sessions_redis.NewStore(10, "tcp", config.RedisAddress, "", []byte("secret"))
	// []byte("secret") 是用于加密会话数据的密钥
	r.Use(sessions.Sessions("mysession", store))
	// 将会话中间件添加到路由,检查是否存在名为 "mysession" 的会话,并在不存在时创建一个

	user := r.Group("/user")
	{
		user.POST("/register", controllers.UserController{}.Register)
		user.POST("/login", controllers.UserController{}.Login)
	}

	player := r.Group("/player")
	{
		player.POST("/list", controllers.PlayerController{}.GetPlayers)
	}

	vote := r.Group("/vote")
	{
		vote.POST("/add", controllers.VoteController{}.AddVote)
	}

	r.POST("/ranking", controllers.PlayerController{}.GetRanking)

	return r
}

宝塔安装及配置Redis配置

<www.bt.cn>

个人电脑不建议安装(可虚拟机安装)

基于Redis的有序集合Sorted Sets优化排序

复制代码
go get github.com/redis/go-redis/v9
复制代码
package config

const (
	RedisAddress  = "localhost:6379"
	RedisPassword = ""
	RedisDb       = 0
)
复制代码
// cache/redis.go
package cache

import (
	"context"
	"godemo/config"

	"github.com/redis/go-redis/v9"
)

var (
	Rdb  *redis.Client
	Rctx context.Context
)

func init() {
	Rdb = redis.NewClient(&redis.Options{
		Addr:     config.RedisAddress,
		Password: config.RedisPassword,
		DB:       config.RedisDb,
	})
	Rctx = context.Background()
}

func Zscore(id int, score int) redis.Z {
	return redis.Z{Score: float64(score), Member: id}
}
复制代码
// controllers/player.go
package controllers

import (
	"godemo/cache"
	"godemo/models"
	"strconv"
	"time"

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

type PlayerController struct{}

func (p PlayerController) GetPlayers(c *gin.Context) {
	aidStr := c.DefaultPostForm("aid", "0")
	aid, _ := strconv.Atoi(aidStr)

	rs, err := models.GetPlayers(aid, "id asc")
	if err != nil {
		ReturnErrer(c, 4004, "没有相关信息")
		return
	}
	ReturnSuccess(c, 0, "success", rs, 1)
}

func (p PlayerController) GetRanking(c *gin.Context) {
	// err := cache.Rdb.Set(cache.Rctx, "name", "zhangsan", 0).Err()
	// if err != nil {
	// 	panic(err)
	// } // 测试

	aidStr := c.DefaultPostForm("aid", "0")
	aid, _ := strconv.Atoi(aidStr)

	var redisKey string
	redisKey = "ranking:" + aidStr
	rs, err := cache.Rdb.ZRevRange(cache.Rctx, redisKey, 0, -1).Result()
	if err == nil && len(rs) > 0 {
		return
	}

	rsDb, errDb := models.GetPlayers(aid, "score desc")
	if errDb == nil {
		for _, value := range rsDb {
			cache.Rdb.ZAdd(cache.Rctx, redisKey, cache.Zscore(value.Id, value.Score)).Err()
		}
		// 设置过期时间
		cache.Rdb.Expire(cache.Rctx, redisKey, 24*time.Hour)
		ReturnSuccess(c, 0, "success", rs, 1)
		return
	}
	ReturnErrer(c, 4004, "没有相关信息")
	return
}
复制代码
// controllers/player.go
package controllers

import (
	"godemo/cache"
	"godemo/models"
	"strconv"
	"time"

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

type PlayerController struct{}

func (p PlayerController) GetPlayers(c *gin.Context) {
	aidStr := c.DefaultPostForm("aid", "0")
	aid, _ := strconv.Atoi(aidStr)

	rs, err := models.GetPlayers(aid, "id asc")
	if err != nil {
		ReturnErrer(c, 4004, "没有相关信息")
		return
	}
	ReturnSuccess(c, 0, "success", rs, 1)
}

func (p PlayerController) GetRanking(c *gin.Context) {
	// err := cache.Rdb.Set(cache.Rctx, "name", "zhangsan", 0).Err()
	// if err != nil {
	// 	panic(err)
	// } // 测试

	aidStr := c.DefaultPostForm("aid", "0")
	aid, _ := strconv.Atoi(aidStr)

	var redisKey string
	redisKey = "ranking:" + aidStr
	rs, err := cache.Rdb.ZRevRange(cache.Rctx, redisKey, 0, -1).Result()
	if err == nil && len(rs) > 0 {
		var players []models.Player
		for _, value := range rs {
			id, _ := strconv.Atoi(value)
			rsInfo, _ := models.GetPlayerInfo(id)
			if rsInfo.Id > 0 {
				players = append(players, rsInfo)
			}
		}
		ReturnSuccess(c, 0, "success", players, 1)
		return
	}

	rsDb, errDb := models.GetPlayers(aid, "score desc")
	if errDb == nil {
		for _, value := range rsDb {
			cache.Rdb.ZAdd(cache.Rctx, redisKey, cache.Zscore(value.Id, value.Score)).Err()
		}
		// 设置过期时间
		cache.Rdb.Expire(cache.Rctx, redisKey, 24*time.Hour)
		ReturnSuccess(c, 0, "success", rs, 1)
		return
	}
	ReturnErrer(c, 4004, "没有相关信息")
	return
}
复制代码
// controllers/vote.go
package controllers

import (
	"godemo/cache"
	"godemo/models"
	"strconv"

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

type VoteController struct{}

func (v VoteController) AddVote(c *gin.Context) {
	userIdStr := c.DefaultPostForm("userId", "0")
	playerIdStr := c.DefaultPostForm("playerId", "0")
	userId, _ := strconv.Atoi(userIdStr)
	playerId, _ := strconv.Atoi(playerIdStr)

	if userId == 0 || playerId == 0 {
		ReturnErrer(c, 4001, "请输入正确的信息")
		return
	}
	user, _ := models.GetUserInfo(userId)
	if user.Id == 0 {
		ReturnErrer(c, 4001, "投票用户不存在")
		return
	}
	player, _ := models.GetPlayerInfo(playerId)
	if player.Id == 0 {
		ReturnErrer(c, 4001, "参赛选手不存在")
		return
	}
	vote, _ := models.GetVoteInfo(userId, playerId)
	if vote.Id != 0 {
		ReturnErrer(c, 4001, "已投票")
		return
	}
	rs, err := models.AddVote(userId, playerId)
	if err == nil {
		// 更新参赛选手分数字段,自增1
		models.UpdatePlayerScore(playerId)
		// 同时更新redis
		var redisKey string
		redisKey = "ranking:" + strconv.Itoa(player.Aid)
		cache.Rdb.ZIncrBy(cache.Rctx, redisKey, 1, strconv.Itoa(playerId))
		ReturnSuccess(c, 0, "投票成功", rs, 1)
		return
	}
	ReturnErrer(c, 4004, "请联系管理员")
	return
}

部署项目并上线

复制代码
go build
// linux系统build
// GOOS=linux GOARCH=amd64 go build
宝塔 nginx

在Linux下部署项目并上线

<!-- -->

go模板语法

复制代码
package main

import (
	"errors"
	"fmt"
	"html/template"
	"net/http"
)

type UserInfo struct {
	Name   string
	Gender string
	Age    int
}

func sayHello(w http.ResponseWriter, _ *http.Request) {
	// http.ResponseWriter用于写入HTTP响应  *http.Request表示HTTP请求
	// 自定义函数
	admire := func(name string, gender string) (string, error) {
		var praise string
		if gender == "男" {
			praise = "真帅气!!!!!!!!"
		} else if gender == "女" {
			praise = "真漂亮!!!!!!!!"
		} else {
			return "", errors.New("invalid gender")
		}
		return name + praise, nil
	}

	// 解析指定文件生成模板对象(并注册自定义函数)
	tmpl, err := template.New("hello.tmpl").Funcs(template.FuncMap{"admire": admire}).ParseFiles("./hello.tmpl")
    // template.New 创建了一个新的模板对象hello.tmpl,Funcs 方法注册了之前定义的 admire 函数,使其可以在模板中使用,使用 ParseFiles 方法解析了当前目录下的 hello.tmpl 文件
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}

	// 利用给定数据渲染模板,并将结果写入w
	user1 := UserInfo{
		Name:   "小王子",
		Gender: "男",
		Age:    17,
	}

	user2 := map[string]interface{}{
		"name":   "小公主",
		"gender": "女",
		"age":    19,
	}

	hobbylist := []string{
		"跑步",
		"听音乐",
		"学习",
	}

	err = tmpl.Execute(w, map[string]interface{}{
		"user1": user1,
		"user2": user2,
		"hobby": hobbylist,
	})
    // Execute 方法将之前准备的数据渲染到模板中,并将结果写入 http.ResponseWriter。这意味着当客户端请求这个路由时,它会收到一个渲染后的HTML页面
	if err != nil {
		return
	}
}

func qianTao(w http.ResponseWriter, _ *http.Request) {

	tmpl, err := template.ParseFiles("./t.tmpl", "./ul.tmpl")
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}
	user := UserInfo{
		Name:   "小王子",
		Gender: "男",
		Age:    17,
	}
	err = tmpl.Execute(w, user)
	if err != nil {
		return
	}

}
func main() {
	http.HandleFunc("/", sayHello)
	http.HandleFunc("/demo", qianTao)
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		fmt.Println("HTTP server failed,err:", err)
		return
	}
}

以下是hello.tmpl示例文件

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello</title>
</head>

<body>
<p>Hello {{.user1.Name}}</p>
<p>性别:{{.user1.Gender}}</p>
<p>年龄:{{.user1.Age}}</p>
<br>
<p>Hello {{.user2.name}}</p>
<p>性别:{{.user2.gender}}</p>
<p>年龄:{{.user2.age}}</p>

{{/*自定义变量*/}}
{{ $a := 100 }}
{{ $b := .user1.Age }}

<hr>

{{/*移除空格*/}}
<p>年龄:{{- .user2.age -}}</p>

<hr>

{{/*条件判断*/}}
{{ if $a}}
    {{$a}}
{{else}}
    a 不存在
{{end}}

<hr>
{{ if lt .user1.Age 18}}
    未成年
{{else}}
    上大学了
{{end}}

<hr>
{{range $index,$hobby :=.hobby}}
    <p>{{$index}}------{{$hobby}}</p>
{{else}}
    没有爱好
{{end}}

<hr>
{{/*with作用域*/}}
{{with .user1}}
    <p>Hello {{.Name}}</p>
    <p>性别:{{.Gender}}</p>
    <p>年龄:{{.Age}}</p>
{{end}}

<hr>
{{index .hobby 2}}
<hr>

{{/*自定义函数*/}}
{{admire .user1.Name .user1.Gender}}
{{admire .user2.name .user2.gender}}
</body>

预定义函数

执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里

预定义的全局函数如下:

复制代码
index
    执行结果为第一个参数以剩下的参数为索引/键指向的值;
    如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
html
    返回与其参数的文本表示形式等效的转义HTML。
    这个函数在html/template中不可用。
urlquery
    以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。
    这个函数在html/template中不可用。
js
    返回与其参数的文本表示形式等效的转义JavaScript。
call
    执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
    如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
    其中Y是函数类型的字段或者字典的值,或者其他类似情况;
    call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
    该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
    如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;

比较函数

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

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

复制代码
eq      如果arg1 == arg2则返回真
ne      如果arg1 != arg2则返回真
lt      如果arg1 < arg2则返回真
le      如果arg1 <= arg2则返回真
gt      如果arg1 > arg2则返回真
ge      如果arg1 >= arg2则返回真

为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:

复制代码
{{eq arg1 arg2 arg3}}

比较函数只适用于基本类型(或重定义的基本类型,如"type Celsius float32")。但是,整数和浮点数不能互相比较

自定义函数

复制代码
func sayHello(w http.ResponseWriter, _ *http.Request) {

	// 自定义函数
	admire := func(name string, gender string) (string, error) {
		var praise string
		if gender == "男" {
			praise = "真帅气!!!!!!!!"
		} else if gender == "女" {
			praise = "真漂亮!!!!!!!!"
		} else {
			return "", errors.New("invalid gender")
		}
		return name + praise, nil
	}

	// 解析指定文件生成模板对象(并注册自定义函数)
	tmpl, err := template.New("hello.tmpl").Funcs(template.FuncMap{"admire": admire}).ParseFiles("./hello.tmpl")
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
    }
}

调用

复制代码
{{admire .user1.Name .user1.Gender}}
{{admire .user2.name .user2.gender}}

模板嵌套

复制代码
func qianTao(w http.ResponseWriter, _ *http.Request) {

	tmpl, err := template.ParseFiles("./t.tmpl", "./ul.tmpl")
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}
	user := UserInfo{
		Name:   "小王子",
		Gender: "男",
		Age:    17,
	}
	err = tmpl.Execute(w, user)
	if err != nil {
		return
	}

t.tmpl文件

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>tmpl test</title>
</head>
<body>

<h1>测试嵌套template语法</h1>
<hr>
{{template "ul.tmpl"}}
<hr>
{{template "ol.tmpl"}}
</body>
</html>

{{ define "ol.tmpl"}}
    <ol>
        <li>吃饭</li>
        <li>睡觉</li>
        <li>打豆豆</li>
    </ol>
{{end}}

<div>你好,{{.Name}}!</div>

ul.html文件

复制代码
<ul>
    <li>注释</li>
    <li>日志</li>
    <li>测试</li>
</ul>

模板继承

main.go文件

复制代码
package main

import (
	"fmt"
	"html/template"
	"net/http"
)

func index(w http.ResponseWriter, _ *http.Request) {
	//定义模板
	//解析模板
	tmpl, err := template.ParseFiles("./base.tmpl", "./index.tmpl")
	if err != nil {
		fmt.Printf("parse error: %v\n", err)
		return
	}
	msg := "hello world"
	//渲染模板
	err = tmpl.ExecuteTemplate(w, "index.tmpl", msg)
	if err != nil {
		return
	}

}

func base(w http.ResponseWriter, _ *http.Request) {
	tmpl, err := template.ParseFiles("./base.tmpl")
	if err != nil {
		fmt.Printf("parse error: %v\n", err)
		return
	}
	msg := "这是base页面"
	//渲染模板
	err = tmpl.Execute(w, msg)
	if err != nil {
		return
	}

}

func main() {
	http.HandleFunc("/index", index)
	http.HandleFunc("/base", base)
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Println("HTTP server failed,err:", err)
		return
	}
}

base.tmpl文件

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>模板继承</title>
    <style>
        {
            margin: 0;
        }
        .nav {
            height: 50px;
            width: 100%;
            position: fixed;
            top: 0;
            background-color: burlywood;
        }

        .main {
            margin-top: 50px;
        }

        .menu {
            width: 20%;
            height: 100%;
            position: fixed;
            left: 0;
            background-color: cornflowerblue;
        }

        .center {
            text-align: center;
        }
    </style>
</head>
<body>
<div class="nav"></div>
<div class="main">
    <div class="menu"></div>
    <div class="content center">
        {{.}}
        {{block "content" .}}
        {{end}}
    </div>

</div>

</body>
</html>

index.tmpl文件

复制代码
{{/*继承根模板*/}}

{{template "base.tmpl" .}}

{{/*重新定义模板*/}}

{{define "content"}}
    <h1>这是index页面</h1>
{{end}}

如果我们的模板名称冲突了,例如不同业务线下都定义了一个index.tmpl模板,我们可以通过下面两种方法来解决

  1. 在模板文件开头使用{``{define 模板名}}语句显式的为模板命名

  2. 可以把模板文件存放在templates文件夹下面的不同目录中,然后使用template.ParseGlob("templates/**/*.tmpl")解析模板

模板补充

修改默认的标识符

Go标准库的模板引擎使用的花括号{``{}}作为标识,而许多前端框架(如VueAngularJS)也使用{``{}}作为标识符,所以当我们同时使用Go语言模板引擎和以上前端框架时就会出现冲突,这个时候我们需要修改标识符,修改前端的或者修改Go语言的。这里演示如何修改Go语言模板引擎默认的标识符:

复制代码
template.New("t.tmpl").Delims("{[", "]}").ParseFiles("./t.tmpl")

最后我们在渲染的时候

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>自定义模板函数</title>
</head>
<body>
<h1>姓名: {[.Name]}</h1>
<h1>性别: {[.Gender]}</h1>
<h1>年龄: {[.Age]}</h1>
</body>
</html>

text/template与html/tempalte的区别

html/template针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击(XSS)

例如,我定义下面的模板文件:

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello</title>
</head>
<body>
    {{.}}
</body>
</html>

这个时候传入一段JS代码并使用html/template去渲染该文件,会在页面上显示出转义后的JS内容

但是在某些场景下,我们如果相信用户输入的内容,不想转义的话,可以自行编写一个safe函数,手动返回一个template.HTML类型的内容。示例如下:

复制代码
func xss(w http.ResponseWriter, r *http.Request){
	tmpl,err := template.New("xss.tmpl").Funcs(template.FuncMap{
		"safe": func(s string)template.HTML {
            // 这告诉模板引擎这个字符串应被视为安全的 HTML,而不是需要转义的文本
			return template.HTML(s)
		},
	}).ParseFiles("./xss.tmpl")
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}
	jsStr := `<script>alert('123')</script>`
    // 这里定义了一个包含恶意 JavaScript 的字符串,目的是测试模板是否正确地转义了这段代码,防止其在页面上被执行
	err = tmpl.Execute(w, jsStr)
	if err != nil {
		fmt.Println(err)
	}
}

这样我们只需要在模板文件不需要转义的内容后面使用我们定义好的safe函数就可以了

复制代码
{{ . | safe }}
复制代码
<!-- xss.tmpl 文件内容 -->  
<div>{{ . }}</div>  
<div>{{ safe . }}</div>

Gin渲染

我们首先定义一个存放模板文件的templates文件夹,然后在其内部按照业务分别定义一个posts文件夹和一个users文件夹。 posts/index.html文件的内容如下:

复制代码
{{define "posts/index.tmpl"}}
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <link rel="stylesheet" href="/xxx/index.css">
        <title>posts/index</title>
    </head>
    <body>
    {{.title |safe}}
    </body>
    <script src="/xxx/index.js"></script>
    </html>
{{end}}

users/index.html文件的内容如下:

复制代码
{{define "users/index.tmpl"}}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>users/index</title>
    </head>
    <body>
    {{.title}}
    </body>
    </html>
{{end}}

Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行HTML模板渲染

复制代码
package main

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

func main() {
	r := gin.Default()

	// 设置静态文件路由,表示以/xxx开头的静态文件都会去statics目录下找
	r.Static("/xxx", "./statics")

	// 设置模板函数
	r.SetFuncMap(template.FuncMap{
		"safe": func(s string) template.HTML {
			return template.HTML(s)
		},
	})

	// 加载模板文件
	r.LoadHTMLGlob("templates/**/*")

	// 处理/posts/index请求
	r.GET("/posts/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
			"title": "<a href= https://uestcwxy.love>wxy的博客</a>",
		})
	})

	// 处理/users/index请求
	r.GET("/users/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
			"title": "https://uestcwxy.top",
		})
	})

	// 启动服务器
	err := r.Run(":9000")
	if err != nil {
		fmt.Println("服务器启动失败")
	}
}
使用模板继承

Gin框架默认都是使用单模板,如果需要使用block template功能,可以通过"github.com/gin-contrib/multitemplate"库实现,具体示例如下:

首先,假设我们项目目录下的templates文件夹下有以下模板文件,其中home.tmplindex.tmpl继承了base.tmpl

复制代码
templates
├── includes
│   ├── home.tmpl
│   └── index.tmpl
├── layouts
│   └── base.tmpl
└── scripts.tmpl

然后我们定义一个loadTemplates函数如下:

复制代码
func loadTemplates(templatesDir string) multitemplate.Renderer {
	// 创建一个新的 multitemplate.Renderer 实例
	r := multitemplate.NewRenderer()

	// 加载 layouts 目录下的模板文件
	layouts, err := filepath.Glob(templatesDir + "/layouts/*.tmpl")
	if err != nil {
		panic(err.Error())
	}

	// 加载 includes 目录下的模板文件
	includes, err := filepath.Glob(templatesDir + "/includes/*.tmpl")
	if err != nil {
		panic(err.Error())
	}

	// 为 layouts/ 和 includes/ 目录生成 templates map
	for _, include := range includes {
		// 创建 layouts 的副本
		layoutCopy := make([]string, len(layouts))
		copy(layoutCopy, layouts)

		// 将 layouts 和 include 组合成一个文件切片
		files := append(layoutCopy, include)

		// 将文件切片添加到 multitemplate.Renderer 实例中
		r.AddFromFiles(filepath.Base(include), files...)
	}

	return r
}

我们在main函数中

复制代码
func indexFunc(c *gin.Context) {
	// 渲染 index.tmpl 模板并返回给客户端
	c.HTML(http.StatusOK, "index.tmpl", nil)
}

func homeFunc(c *gin.Context) {
	// 渲染 home.tmpl 模板并返回给客户端
	c.HTML(http.StatusOK, "home.tmpl", nil)
}

func main() {
	// 创建一个默认的 Gin 引擎实例
	r := gin.Default()

	// 加载模板文件,并将返回的 multitemplate.Renderer 实例赋值给 Gin 引擎的 HTMLRender 字段
	r.HTMLRender = loadTemplates("./templates")

	// 设置路由处理函数,处理 /index 请求
	r.GET("/index", indexFunc)

	// 设置路由处理函数,处理 /home 请求
	r.GET("/home", homeFunc)

	// 启动服务器,监听默认端口
	r.Run()
}
补充文件路径处理

关于模板文件和静态文件的路径,我们需要根据公司/项目的要求进行设置。可以使用下面的函数获取当前执行程序的路径。

复制代码
import (
	"os"
	"path/filepath"
)

func getCurrentPath() string {
	// 获取可执行文件的路径
	if ex, err := os.Executable(); err == nil {
		// 返回可执行文件的目录路径
		return filepath.Dir(ex)
	}
	// 如果获取路径失败,则返回当前目录路径
	return "./"
}
JSON渲染
复制代码
package main

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

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!",
			"name":    "wxy",
		})
	})

	r.GET("/moreJSON", func(c *gin.Context) {
		// 方法二:使用结构体
		type msg struct {
			Name    string `json:"name"`
			Message string `json:"message"`
			Age     int    `json:"age"`
		}
		data := msg{"121", "hh", 18}
		c.JSON(http.StatusOK, data)
	})
	err := r.Run(":9090")
	if err != nil {
		return
	}
}
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 = "小王子"
		msg.Message = "Hello world!"
		msg.Age = 18
		c.XML(http.StatusOK, msg)
	})
	r.Run(":8080")
}
YMAL渲染
复制代码
r.GET("/someYAML", func(c *gin.Context) {
	c.YAML(http.StatusOK, gin.H{"message": "ok", "status": http.StatusOK})
})
protobuf渲染
复制代码
r.GET("/someProtoBuf", func(c *gin.Context) {
	reps := []int64{int64(1), int64(2)}
	label := "test"
	// protobuf 的具体定义写在 testdata/protoexample 文件中。
	data := &protoexample.Test{
		Label: &label,
		Reps:  reps,
	}
	// 请注意,数据在响应中变为二进制数据
	// 将输出被 protoexample.Test protobuf 序列化了的数据
	c.ProtoBuf(http.StatusOK, data)
})

获取参数

获取querystring参数

querystring指的是URL中?后面携带的参数,例如:/user/search?username=wxy&address=沙河校区。 获取请求的querystring参数的方法如下:

复制代码
package main

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

func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.GET("/user/search", func(c *gin.Context) {
		username := c.DefaultQuery("username", "wxy")
		//username := c.Query("username")
		address := c.Query("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})
	err := r.Run()
	if err != nil {
		return
	}
}
获取form参数

当前端请求的数据通过form表单提交时,例如向/user/search发送一个POST请求,获取请求数据的方式如下:

复制代码
func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.POST("/user/search", func(c *gin.Context) {
		// DefaultPostForm取不到值时会返回指定的默认值
		//username := c.DefaultPostForm("username", "wxy")
		username, ok := c.GetPostForm("username")
		if !ok {
			username = "hhh"
		}
		//username := c.PostForm("username")
		address := c.PostForm("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})

	err := r.Run(":8080")
	if err != nil {
		return
	}
}
获取json参数

当前端请求的数据通过JSON提交时,例如向/json发送一个POST请求,则获取请求参数的方式如下:

复制代码
r.POST("/json", func(c *gin.Context) {
	// 注意:下面为了举例子方便,暂时忽略了错误处理
	b, _ := c.GetRawData()  // 从c.Request.Body读取请求数据
	// 定义map或结构体
	var m map[string]interface{}
	// 反序列化
	_ = json.Unmarshal(b, &m)

	c.JSON(http.StatusOK, m)
})

更便利的获取请求参数的方式,参见下面的参数绑定小节

获取path参数

请求的参数通过URL路径传递,例如:/user/search/wxy/沙河校区。 获取请求URL路径中的参数的方式如下。

复制代码
func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.GET("/user/search/:username/:address", func(c *gin.Context) {
		username := c.Param("username")
		address := c.Param("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})

	err := r.Run(":8080")
	if err != nil {
		return
	}
}
参数绑定

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

复制代码
package main

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

// Login 结构体用于绑定JSON数据
type Login struct {
	User     string `form:"user" json:"user" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

func main() {
	r := gin.Default()

	// 处理绑定JSON的示例请求 ({"user": "wxy", "password": "123456"})
	r.POST("/loginJSON", func(c *gin.Context) {
		var login Login

		// 将请求中的JSON数据绑定到Login结构体
		if err := c.ShouldBind(&login); err == nil {
			fmt.Printf("登录信息:%#v\n", login)
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 处理绑定form表单的示例请求 (user=q1mi&password=123456)
	r.POST("/loginForm", func(c *gin.Context) {
		var login Login

		// 根据请求的Content-Type自动选择绑定器进行绑定
		if err := c.ShouldBind(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 处理绑定QueryString的示例请求 (/loginQuery?user=q1mi&password=123456)
	r.GET("/loginForm", func(c *gin.Context) {
		var login Login

		// 根据请求的Content-Type自动选择绑定器进行绑定
		if err := c.ShouldBind(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 监听并在0.0.0.0:8080上提供服务
	err := r.Run(":8080")
	if err != nil {
		return
	}
}

ShouldBind 函数会根据请求的方法和内容类型选择适当的绑定引擎进行数据绑定。对于 GET 请求,只使用查询参数绑定;对于 POST 请求,优先考虑 JSON 或 XML 数据绑定,如果不是 JSON 或 XML,则使用表单数据绑定。这样可以方便地将请求中的数据解析并绑定到结构体中,以便在处理请求时使用这些数据

文件上传

单个文件上传

文件上传前端页面代码:

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>上传文件示例</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="f1">
    <input type="submit" value="上传">
</form>
</body>
</html>

后端gin框架部分代码:

复制代码
func main() {
	router := gin.Default()
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// 单个文件
		file, err := c.FormFile("f1")
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"message": err.Error(),
			})
			return
		}

		log.Println(file.Filename)
		dst := fmt.Sprintf("C:/tmp/%s", file.Filename)
		// 上传文件到指定的目录
		c.SaveUploadedFile(file, dst)
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("'%s' uploaded!", file.Filename),
		})
	})
	router.Run()
}
多个文件上传
复制代码
func main() {
	router := gin.Default()
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// Multipart form
		form, _ := c.MultipartForm()
		files := form.File["file"]

		for index, file := range files {
			log.Println(file.Filename)
			dst := fmt.Sprintf("./upload/%s_%d", file.Filename, index)
			// 上传文件到指定的目录
			c.SaveUploadedFile(file, dst)
		}
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("%d files uploaded!", len(files)),
		})
	})
	router.Run()
}

重定向

HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。

复制代码
package main

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

func main() {
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com/")
	})
	// Listen and serve on 0.0.0.0:8080
	err := r.Run(":8080")
	if err != nil {
		return
	}
}
路由重定向

路由重定向,使用HandleContext

复制代码
package main

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

func main() {
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		// 指定重定向的URL
		c.Request.URL.Path = "/test2"
		r.HandleContext(c)
	})
	r.GET("/test2", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"hello": "world"})
	})
	// Listen and serve on 0.0.0.0:8080
	err := r.Run(":8080")
	if err != nil {
		return
	}
}

Gin路由

普通路由
复制代码
r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})

此外,还有一个可以匹配所有请求方法的Any方法如下:

复制代码
package main

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

func main() {
	r := gin.Default()
	r.Any("/user", func(c *gin.Context) {
		switch c.Request.Method {
		case http.MethodGet:
			c.JSON(http.StatusOK, gin.H{"method": http.MethodGet})
		case http.MethodPost:
			c.JSON(http.StatusOK, gin.H{"method": http.MethodPost})
		}
	})
	err := r.Run()
	if err != nil {
		fmt.Println(err.Error())
	}
}

为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。

复制代码
r.NoRoute(func(c *gin.Context) {
		c.HTML(http.StatusNotFound, "views/404.html", nil)
	})
路由组

我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。

复制代码
package main

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

func main() {
	r := gin.Default()
	userGroup := r.Group("/user")
	{
		userGroup.GET("/index", func(c *gin.Context) {})
		userGroup.GET("/login", func(c *gin.Context) {})
		userGroup.POST("/login", func(c *gin.Context) {})
	}
	shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/index", func(c *gin.Context) {})
		shopGroup.GET("/cart", func(c *gin.Context) {})
		shopGroup.POST("/checkout", func(c *gin.Context) {})
	}
	err := r.Run()
	if err != nil {
		fmt.Println(err.Error())
	}
}

路由组也是支持嵌套的,例如:

复制代码
shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/index", func(c *gin.Context) {...})
		shopGroup.GET("/cart", func(c *gin.Context) {...})
		shopGroup.POST("/checkout", func(c *gin.Context) {...})
		// 嵌套路由组
		xx := shopGroup.Group("xx")
		xx.GET("/oo", func(c *gin.Context) {...})
	}

通常我们将路由分组用在划分业务逻辑或划分API版本时。

路由原理

Gin框架的路由原理是使用前缀树的方式实现的动态路由。它使用了定制版本的httprouter,其路由原理是大量使用公共前缀的树结构,基本上是一个紧凑的Trie tree(或者只是Radix Tree)。

Gin中间件

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

定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。

记录接口耗时的中间件

例如我们像下面的代码一样定义一个统计请求耗时的中间件。

复制代码
// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Set("name", "wxy") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
		// 调用该请求的剩余处理程序
		c.Next()
		// 不调用该请求的剩余处理程序
		// c.Abort()
		// 计算耗时
		cost := time.Since(start)
		log.Println(cost)
	}
}
记录响应体的中间件

我们有时候可能会想要记录下某些情况下返回给客户端的响应数据,这个时候就可以编写一个中间件来搞定。

复制代码
type bodyLogWriter struct {
	gin.ResponseWriter               // 嵌入gin框架ResponseWriter
	body               *bytes.Buffer // 我们记录用的response
}

// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)                  // 我们记录一份
	return w.ResponseWriter.Write(b) // 真正写入响应
}

// ginBodyLogMiddleware 一个记录返回给客户端响应体的中间件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func ginBodyLogMiddleware(c *gin.Context) {
	blw := &bodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: c.Writer}
	c.Writer = blw // 使用我们自定义的类型替换默认的

	c.Next() // 执行业务逻辑

	fmt.Println("Response body: " + blw.body.String()) // 事后按需记录返回的响应
}
跨域中间件cors

推荐使用社区的https://github.com/gin-contrib/cors 库,一行代码解决前后端分离架构下的跨域问题。

注意: 该中间件需要注册在业务处理函数前面。

这个库支持各种常用的配置项,具体使用方法如下。

复制代码
package main

import (
  "time"

  "github.com/gin-contrib/cors"
  "github.com/gin-gonic/gin"
)

func main() {
  router := gin.Default()
  // CORS for https://foo.com and https://github.com origins, allowing:
  // - PUT and PATCH methods
  // - Origin header
  // - Credentials share
  // - Preflight requests cached for 12 hours
  router.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://foo.com"},  // 允许跨域发来请求的网站
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE",  "OPTIONS"},  // 允许的请求方法
    AllowHeaders:     []string{"Origin", "Authorization", "Content-Type"},
    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: true,
    AllowOriginFunc: func(origin string) bool {  // 自定义过滤源站的方法
      return origin == "https://github.com"
    },
    MaxAge: 12 * time.Hour,
  }))
  router.Run()
}

当然你可以简单的像下面的示例代码那样使用默认配置,允许所有的跨域请求。

复制代码
func main() {
  router := gin.Default()
  // same as
  // config := cors.DefaultConfig()
  // config.AllowAllOrigins = true
  // router.Use(cors.New(config))
  router.Use(cors.Default())
  router.Run()
}
注册中间件

在gin框架中,我们可以为每个路由添加任意数量的中间件。

为全局路由注册
复制代码
func main() {
	// 新建一个没有任何默认中间件的路由
	r := gin.New()
	// 注册一个全局中间件
	r.Use(StatCost())
	
	r.GET("/test", func(c *gin.Context) {
		name := c.MustGet("name").(string) // 从上下文取值
		log.Println(name)
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello world!",
		})
	})
	r.Run()
}
为某个路由单独注册
复制代码
// 给/test2路由单独注册中间件(可注册多个)
	r.GET("/test2", StatCost(), func(c *gin.Context) {
		name := c.MustGet("name").(string) // 从上下文取值
		log.Println(name)
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello world!",
		})
	})
为路由组注册中间件

为路由组注册中间件有以下两种写法

写法1:

复制代码
shopGroup := r.Group("/shop", StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

写法2:

复制代码
shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}
中间件注意事项
gin默认中间件

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

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release

  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

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

gin中间件中使用goroutine

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

运行多个服务

我们可以在多个端口启动服务,例如:

复制代码
package main

import (
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/sync/errgroup"
)

var (
	g errgroup.Group
)

func router01() http.Handler {
	e := gin.New()
	e.Use(gin.Recovery())
	e.GET("/", func(c *gin.Context) {
		c.JSON(
			http.StatusOK,
			gin.H{
				"code":  http.StatusOK,
				"error": "Welcome server 01",
			},
		)
	})

	return e
}

func router02() http.Handler {
	e := gin.New()
	e.Use(gin.Recovery())
	e.GET("/", func(c *gin.Context) {
		c.JSON(
			http.StatusOK,
			gin.H{
				"code":  http.StatusOK,
				"error": "Welcome server 02",
			},
		)
	})

	return e
}

func main() {
	server01 := &http.Server{
		Addr:         ":8080",
		Handler:      router01(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	server02 := &http.Server{
		Addr:         ":8081",
		Handler:      router02(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	// 借助 errgroup.Group 或者自行开启两个 goroutine 分别启动两个服务
	g.Go(func() error {
		// 启动 server01 服务
		return server01.ListenAndServe()
	})

	g.Go(func() error {
		// 启动 server02 服务
		return server02.ListenAndServe()
	})

	// 等待所有 goroutine 完成,并返回可能发生的错误
	if err := g.Wait(); err != nil {
		log.Fatal(err)
	}
}

gin注册路由流程哔哩哔哩bilibili

相关推荐
孤寂大仙v8 分钟前
【Linux笔记】理解文件系统(上)
linux·运维·笔记
ianozo10 分钟前
数据结构--【栈与队列】笔记
数据结构·笔记
极客BIM工作室1 小时前
机器学校的考试风波:误差分析、过拟合和欠拟合
笔记·机器学习
flashier1 小时前
C语言 进阶指针学习笔记
c语言·笔记·学习
大白的编程日记.2 小时前
【Linux学习笔记】Linux基本指令分析和权限的概念
linux·笔记·学习
螺旋式上升abc2 小时前
GO语言学习笔记
笔记·学习·golang
W起名有点难2 小时前
前端学习——CSS
前端·css·学习
Moonnnn.3 小时前
51单片机——汇编工程建立、仿真、调试全过程
汇编·笔记·嵌入式硬件·学习·51单片机
画个逗号给明天"3 小时前
TinyWebServer项目笔记——02 半同步半反应堆线程池
笔记
不会聊天真君6473 小时前
HTML5(Web前端开发笔记第一期)
笔记·html5