张裕 发表于 2025-3-10 16:42:52

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(mapinterface{})
        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"`
        Cidint    `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 mapinterface{}
        // 包装为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 mapinterface{}
                // 包装为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 mapinterface{}
                // 包装为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(, {
         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_nameblog.fengfengzhidao.com;

    location / {
      try_files $uri $uri/ /index.html;
      root   /opt/gvb/web/dist;
      indexindex.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 {
        Codeint         `json:"code"`
        Msg   interface{} `json:"msg"`
        Datainterface{} `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 {
        Codeint         `json:"code"`
        Msg   interface{} `json:"msg"`
        Datainterface{} `json:"data"`
        Count int64       `json:"count"`
}

type JsonErrStruct struct {
        Code int         `json:"code"`
        Msginterface{} `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(mapinterface{})
        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"`
        Cidint    `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(mapinterface{}{"mysql conent error": err})
        }
        if Db.Error != nil {
                pkg.LogrusObj.Error(mapinterface{}{"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 {
        Codeint         `json:"code"`
        Msg   interface{} `json:"msg"`
        Datainterface{} `json:"data"`
        Count int64       `json:"count"`
}

type JsonErrStruct struct {
        Code int         `json:"code"`
        Msginterface{} `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{
                                // mapinterface{}的缩写
                                "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"`
        AddTimeint64 `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 := mapinterface{}{
                "name":   "小公主",
                "gender": "女",
                "age":    19,
        }

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

        err = tmpl.Execute(w, mapinterface{}{
                "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的值;每个被索引的主体必须是数组、切片或者字典。
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模板,我们可以通过下面两种方法来解决

[*] 在模板文件开头使用{{define 模板名}}语句显式的为模板命名
[*] 可以把模板文件存放在templates文件夹下面的差别目录中,然后使用template.ParseGlob("templates/**/*.tmpl")解析模板

模板补充

修改默认的标识符

Go标准库的模板引擎使用的花括号{{和}}作为标识,而很多前端框架(如Vue和 AngularJS)也使用{{和}}作为标识符,以是当我们同时使用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.tmpl和index.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 是mapinterface{}的缩写
        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 是mapinterface{}的缩写
        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 mapinterface{}
        // 反序列化
        _ = 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辨认请求数据类型并使用反射机制主动提取请求中QueryString、form表单、JSON、XML等参数到布局体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求主动提取JSON、form表单和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()默认使用了Logger和Recovery中间件,其中:


[*] 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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Gin学习笔记