Gin-《GO开发知识笔记》

admin 2025-11-04 01:03:01 编程 来源:ZONE.CI 全球网 0 阅读模式
  • 1、hello word
  • 2、路由
    • 2.1、基本路由
    • 2.2、路由组
    • 2.3、RESTful API
  • 3、参数解析
    • 3.1、API参数
    • 3.2 、获取querystring参数
    • 3.3、获取表单参数
    • 3.4 、数据解析与绑定
  • 4、文件上传
    • 4.1、单文件上传
    • 4.2、多文件上传
    • 4.3、使用FastDFS实现文件上传
  • 5、重定向
    • 5.1、http重定向
    • 5.2、路由重定向
  • 6、Gin渲染
    • 6.1、HTML渲染
    • 6.2、自定义模板函数
    • 6.3、静态文件处理
  • 7、中间件
    • 7.1、定义全局中间件
    • 7.2、注册中间件
      • 7.2.1、为全局路由注册
      • 7.2.2、为某一个路由单独注册(可注册多个)
      • 7.2.3、为某一个方法注册中间件
      • 7.2.4、为路由组注册中间件
    • 7.3、中间件注意事项
  • 8、会话控制
    • 8.1、Cookie
      • 8.1.1、Go操作cookie
      • 8.1.2、Gin中操作Cookie
    • 8.2、Session
    • 8.3、JWT
      • 8.3.1、生成和解析JWT
  • 9、Gin中使用goroutine
  • 10、运行多个服务
  • 11、图片验证码
  • GoWeb服务器搭建
  • FastDFS分布式文件服务器搭建以及Golang和Python调用
  • Golang操作Json
  • Go测试

    Gin是Golang的一个后端框架,封装比较优雅,API友好。

    1. go get -u github.com/gin-gonic/gin

    1、hello word

    1. package main
    2. import (
    3. "github.com/gin-gonic/gin"
    4. "net/http"
    5. )
    6. func main() {
    7. r := gin.Default() // 创建引擎
    8. // 绑定路由规则,执行函数,gin.Context封装了request和response
    9. r.GET("/", func(c *gin.Context) {
    10. c.String(http.StatusOK,"hello world")
    11. })
    12. r.Run() // 监听端口,默认是:8080
    13. }

    2、路由

    2.1、基本路由

    gin 框架中采用的路由库是基于httprouter做的地址为:https://github.com/julienschmidt/httprouter

    1. r.GET("/",func(c *gin.Context){...})
    2. r.POST("/",func(c *gin.Context){...})
    3. r.PUT("/",func(c *gin.Context){...})
    4. r.DELETE("/",func(c *gin.Context){...})

    此外还有一个匹配所有的请求方法Any

    1. r.Any("/",func(c *gin.Context){...})

    2.2、路由组

    我们可以将拥有共同前缀的url划为一个路由组,习惯性用{}包裹同组路由,只是为了看着清晰。

    1. func main() {
    2. r := gin.Default()
    3. user := r.Group("user")
    4. {
    5. user.GET("/index", func(c *gin.Context) {
    6. c.String(http.StatusOK,"GET")
    7. })
    8. user.POST("/index", func(c *gin.Context) {
    9. c.String(http.StatusOK,"POST")
    10. })
    11. }
    12. r.Run()
    13. }

    如上,我们可以借助postman工具,使用GET和POST请求localhost:8080/user/index,会分别返回GET和POST。同时呢,路由组也是支持嵌套的。

    1. user := r.Group("/user")
    2. {
    3. user.GET("/index", func(c *gin.Context) {
    4. c.String(http.StatusOK,"GET")
    5. })
    6. user.POST("/index", func(c *gin.Context) {
    7. c.String(http.StatusOK,"POST")
    8. })
    9. // 嵌套路由组
    10. boy := user.Group("/boy")
    11. {
    12. boy.GET("/index", func(c *gin.Context) {
    13. c.String(http.StatusOK,"boy")
    14. })
    15. }
    16. }

    同样,我们在postman进行测试,使用get方式访问localhost:8080/user/boy/index,返回boy。通常我们将路由组分在划分业务逻辑或划分API版本。

    2.3、RESTful API

    REST与技术无关,代表一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。简单来说,就是客户端与服务器之间进行交互时候,使用HTTP协议中4个请求方法代表不同的动作。GET(获取资源),POST(新建资源),PUT(更新资源),DELETE(删除资源)只要API程序尊徐了REST风格,那么就可以称其为RESTful API。比如我们现在要编写一个学生管理系统,我们可以对一个学生进行查询,创建,更新,删除等操作。按照以往经验,我们会设计成如下模式:

    请求方法 URL 含义
    GET /get_student 查询学生信息
    POST /create_student 创建学生信息
    PUT /update_student 更新学生信息
    DELETE /delete_student 删除学生信息

    同样的需求我们使用RESTful API设计:

    请求方式 URL 含义
    GET /student 查询学生信息
    POST /student 创建学生信息
    PUT /student 更新学生信息
    DELETE /student 删除学生信息

    如果足够细心的话,会发现上面我们路由组里面其实已经用到了这种方式,只不过一般返回数据是JSON格式。

    1. func main() {
    2. r := gin.Default() // 创建路由
    3. r.GET("/student", func(c *gin.Context) {
    4. // 我们在返回数据的时候,可以使用map,也可以使用gin.H。
    5. c.JSON(http.StatusOK, map[string]interface{}{
    6. "message":"get",
    7. })
    8. })
    9. r.POST("/student", func(c *gin.Context) {
    10. c.JSON(http.StatusOK,gin.H{ //我们在返回数据的时候,可以使用map,也可以使用gin.H。
    11. "message":"post",
    12. })
    13. })
    14. r.Run()
    15. }

    3、参数解析

    3.1、API参数

    请求的参数通过url路径进行传递,例如:/user/Negan/救世军。获取请求参数如下:注意:出现汉字使用Chrome浏览器测试,postman不能解析中文。

    1. func main() {
    2. r := gin.Default()
    3. r.GET("/user/:name/:title", func(c *gin.Context) {
    4. name := c.Param("name")
    5. title := c.Param("title")
    6. c.JSON(http.StatusOK,gin.H{
    7. "name":name,
    8. "title":title,
    9. })
    10. })
    11. r.Run()
    12. }

    3.2 、获取querystring参数

    querystring是指URL中?后面携带的参数,例如:/user?name=Negan&tag=救世军。获取请求参数如下:注意:querystring参数可以通过DefaultQuery()和Query()两个方法获取,前者如果不存在,则返回一个默认值,后者不存在则返回空字符串。

    1. func main() {
    2. r := gin.Default()
    3. r.GET("/user", func(c *gin.Context) {
    4. name := c.DefaultQuery("name","Rick") // 如果不存在,就使用默认
    5. tag := c.Query("tag") // 如果不存在,则返回空字符串
    6. c.JSON(http.StatusOK,gin.H{
    7. "name":name,
    8. "tag":tag,
    9. })
    10. })
    11. r.Run()
    12. }

    3.3、获取表单参数

    表单传输为POST请求,http创建的传世格式为四种:

    • application/json
    • application/x-www/form-urlencoded
    • application/xml
    • multipart/form-data

    同样,表单参数的获取,gin框架也为我们提供了两种方法,DefaultPostForm()和PostForm()方法。前者如果获取不到会返回一个默认值,后者会返回一个空字符串。

    1. func main() {
    2. r := gin.Default()
    3. r.POST("/", func(c *gin.Context) {
    4. name := c.PostForm("name")
    5. tag := c.DefaultPostForm("tag","Boss")
    6. c.JSON(http.StatusOK,gin.H{
    7. "name":name,
    8. "tag":tag,
    9. })
    10. })
    11. r.Run()
    12. }

    3.4 、数据解析与绑定

    为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString、Form、Json、XML以及URI等参数到结构体中。注意:解析的数据必须存在,若接收空值则报错。

    1. type Login struct {
    2. User string `form:"user" json:"user" uri:"user" binding:"required"`
    3. Password string `form:"password" json:"password" uri:"password" binding:"required"`
    4. }
    5. func main() {
    6. r := gin.Default()
    7. login := Login{} // 声明一个结构体
    8. // 绑定json示例 {"user":"Negan","password":"123456"}
    9. r.POST("/json", func(c *gin.Context) {
    10. // c.ShouldBind() 通吃,会根据content-type自动推导
    11. if err := c.ShouldBindJSON(&login); err != nil{
    12. c.JSON(http.StatusBadRequest,gin.H{
    13. "error":err.Error(),
    14. })
    15. return
    16. }
    17. c.JSON(http.StatusOK,gin.H{
    18. "user": login.User,
    19. "password": login.Password,
    20. })
    21. })
    22. // 绑定form表单示例,我们直接在postman上进行测试
    23. r.POST("/form", func(c *gin.Context) {
    24. if err := c.ShouldBind(&login);err != nil{ //ShouldBind()会自动推导
    25. c.JSON(http.StatusBadRequest,gin.H{
    26. "error":err.Error(),
    27. })
    28. return
    29. }
    30. c.JSON(http.StatusOK,gin.H{
    31. "user":login.User,
    32. "password":login.Password,
    33. })
    34. })
    35. // 绑定QueryString参数
    36. r.GET("/query", func(c *gin.Context) {
    37. if err := c.ShouldBindQuery(&login); err != nil{
    38. c.JSON(http.StatusBadRequest,gin.H{
    39. "error":err.Error(),
    40. })
    41. return
    42. }
    43. c.JSON(http.StatusOK,gin.H{
    44. "user":login.User,
    45. "password":login.Password,
    46. })
    47. })
    48. // 绑定API参数
    49. r.GET("/api/:user/:password", func(c *gin.Context) {
    50. if err := c.ShouldBindUri(&login); err != nil{
    51. c.JSON(http.StatusBadRequest,gin.H{
    52. "error":err.Error(),
    53. })
    54. return
    55. }
    56. c.JSON(http.StatusOK,gin.H{
    57. "user":login.User,
    58. "password":login.Password,
    59. })
    60. })
    61. r.Run()
    62. }

    4、文件上传

    4.1、单文件上传

    1. package main
    2. import (
    3. "fmt"
    4. "github.com/gin-gonic/gin"
    5. "log"
    6. "net/http"
    7. )
    8. func main() {
    9. r := gin.Default()
    10. // 处理multipart forms提交文件时默认的内存限制是32MB
    11. r.MaxMultipartMemory = 8 << 20 // 修改为8MB
    12. r.POST("/upload", func(c *gin.Context) {
    13. // 单个文件
    14. file, err := c.FormFile("file") // 表单的name
    15. if err != nil{
    16. c.JSON(http.StatusInternalServerError, gin.H{
    17. "message":err.Error(),
    18. })
    19. return
    20. }
    21. log.Println(file.Filename)
    22. dst := fmt.Sprintf("H:\\GinDemo\\lesson03\\%s", file.Filename) // 拼接文件保存路径
    23. if err := c.SaveUploadedFile(file, dst); err != nil{
    24. c.JSON(http.StatusInternalServerError,gin.H{
    25. "message":err.Error(),
    26. })
    27. return
    28. }
    29. c.JSON(http.StatusOK,gin.H{
    30. "message":fmt.Sprintf("%s upload", file.Filename),
    31. })
    32. })
    33. r.Run()
    34. }

    4.2、多文件上传

    1. func main() {
    2. r := gin.Default()
    3. // 处理multipart forms提交文件时默认的内存限制是32MB
    4. r.MaxMultipartMemory = 8 << 20 // 修改为8MB
    5. r.POST("/upload", func(c *gin.Context) {
    6. form, _ := c.MultipartForm() // 多文件
    7. files := form.File["files"] // 表单的name
    8. for index, file := range files{
    9. log.Println(file.Filename)
    10. dst := fmt.Sprintf("H:\\GinDemo\\lesson03\\%s_%d",file.Filename,index)
    11. // 上传文件到指定目录
    12. c.SaveUploadedFile(file,dst)
    13. }
    14. c.JSON(http.StatusOK,gin.H{
    15. "message":"文件上传能完成",
    16. })
    17. })
    18. r.Run()
    19. }

    4.3、使用FastDFS实现文件上传

    1. package tool
    2. import (
    3. "bufio"
    4. "fmt"
    5. "github.com/tedcy/fdfs_client"
    6. "os"
    7. "strings"
    8. )
    9. // 上传文件到fastDFS系统
    10. func UploadFile(fileName string)string{
    11. client, err := fdfs_client.NewClientWithConfig("./config/fastdfs.conf")
    12. if err != nil{
    13. fmt.Println("打开fast客户端失败",err.Error())
    14. return ""
    15. }
    16. defer client.Destory()
    17. fileId, err := client.UploadByFilename(fileName)
    18. if err != nil{
    19. fmt.Println("上传文件失败",err.Error())
    20. return ""
    21. }
    22. return fileId
    23. }
    24. // 从配置文件中读取服务器的ip和端口配置
    25. func FileServerAddr() string{
    26. file,err := os.Open("./config/fastdfs.conf")
    27. if err != nil{
    28. fmt.Println(err)
    29. return ""
    30. }
    31. reader := bufio.NewReader(file)
    32. for{
    33. line, err := reader.ReadString('\n')
    34. line = strings.TrimSpace(line)
    35. if err != nil{
    36. return ""
    37. }
    38. line = strings.TrimSuffix(line,"\n")
    39. str := strings.SplitN(line,"=",2)
    40. switch str[0] {
    41. case "http_server_port":return str[1]
    42. }
    43. }
    44. }
    45. // 下载文件
    46. func DownLoadFile(fileId,tempFile string){
    47. client, err := fdfs_client.NewClientWithConfig("./config/fastdfs.conf")
    48. if err != nil{
    49. fmt.Println("打开fast客户端失败",err.Error())
    50. return
    51. }
    52. defer client.Destory()
    53. if err = client.DownloadToFile(fileId,tempFile,0,0);err != nil{
    54. fmt.Println("下载文件失败", err.Error())
    55. return
    56. }
    57. }
    58. // 删除
    59. func DeleteFile(fileId string){
    60. client, err := fdfs_client.NewClientWithConfig("./config/fastdfs.conf")
    61. if err != nil{
    62. fmt.Println("打开fast客户端失败",err.Error())
    63. return
    64. }
    65. defer client.Destory()
    66. if err = client.DeleteFile(fileId);err != nil{
    67. fmt.Println("删除文件失败", err.Error())
    68. return
    69. }
    70. }

    配置文件

    1. tracker_server=123.56.243.64:22122
    2. http_server_port=http://123.56.243.64:80
    3. maxConns=100

    5、重定向

    5.1、http重定向

    1. r.GET("/", func(c *gin.Context) {
    2. c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
    3. })

    5.2、路由重定向

    使用HandleContext

    1. // 路由重定向
    2. r.GET("/a", func(c *gin.Context) {
    3. // 跳转到/b对应的路由处理函数
    4. c.Request.URL.Path = "/b" // 把请求的uri修改
    5. r.HandleContext(c) // 继续后续的处理
    6. })
    7. r.GET("/b", func(c *gin.Context) {
    8. c.JSON(http.StatusOK,gin.H{
    9. "msg": "BBBBBB",
    10. })
    11. })

    6、Gin渲染

    6.1、HTML渲染

    Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行HTML模板渲染。LoadHTMLGlob()可以加载路径下所有的模板文件,LoadHTMLFiles()加载模板文件需要我们自己填入。我们定义一个存放模板文件的templates文件夹,然后在内部分别定义一个users和posts文件夹。里面分别定义同名文件index.tmpl。users\index.tmpl文件内容:

    1. {{define "users/index.tmpl"}}
    2. <!doctype html>
    3. <html lang="en">
    4. <head>
    5. <meta charset="UTF-8">
    6. <meta name="viewport"
    7. content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    8. <meta http-equiv="X-UA-Compatible" content="ie=edge">
    9. <link rel="stylesheet" href="/xxx/index.css">
    10. <title>Document</title>
    11. </head>
    12. <body>
    13. {{ .title }}
    14. </body>
    15. </html>
    16. {{end}}

    posts\index.tmpl文件内容:

    1. {{define "posts/index.tmpl"}}
    2. <!doctype html>
    3. <html lang="en">
    4. <head>
    5. <meta charset="UTF-8">
    6. <meta name="viewport"
    7. content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    8. <meta http-equiv="X-UA-Compatible" content="ie=edge">
    9. <title>Document</title>
    10. </head>
    11. <body>
    12. {{ .title }}
    13. </body>
    14. </html>
    15. {{end}}

    我们很容易发现,HTML文件开头和结尾我们定义了{{define}}和{{end}},Gin框架在进行模板渲染时候会根据这个我们定义的名字进行查找文件。{{.title}}则是Gin后端给我们传过来的数据。Gin后端代码如下:

    1. package main
    2. import (
    3. "github.com/gin-gonic/gin"
    4. "html/template"
    5. )
    6. func main() {
    7. r := gin.Default()
    8. // 解析模板
    9. // r.LoadHTMLFiles("templates/users/index.tmpl","templates/posts/index.tmpl")
    10. r.LoadHTMLGlob("templates/**/*") // 加载所有
    11. r.GET("/posts", func(c *gin.Context) {
    12. c.HTML(200,"posts/index.tmpl",gin.H{ // 模板渲染
    13. "title":"我是posts页面",
    14. })
    15. })
    16. r.GET("/users", func(c *gin.Context) {
    17. c.HTML(200,"users/index.tmpl",gin.H{ // 模板渲染
    18. "title":"<a href='http://www.baidu.com'>百度一下</a>",
    19. })
    20. })
    21. r.Run() // 启动server
    22. }

    我们分别访问localhost:8080/posts和localhost:8080/users,会得到不同的页面。当我们访问localhost:8080/users时,我们会发现浏览器显示的是百度一下,和我们预期的不一样,我们预期的是,页面上应该显示一个超链接才对。但是我们看到的是直接当字符串解析了。那么怎么解决这个问题呢?

    6.2、自定义模板函数

    接上面的问题,我们可以定义一个不转义相应内容的safe模板函数。

    1. package main
    2. import (
    3. "github.com/gin-gonic/gin"
    4. "html/template"
    5. )
    6. func main() {
    7. r := gin.Default()
    8. // gin框架中添加自定义模板函数
    9. r.SetFuncMap(template.FuncMap{
    10. "safe": func(s string) template.HTML {
    11. return template.HTML(s)
    12. },
    13. })
    14. // 解析模板
    15. r.LoadHTMLGlob("templates/**/*") // 加载所有
    16. r.GET("/users", func(c *gin.Context) {
    17. c.HTML(200,"users/index.tmpl",gin.H{ // 模板渲染
    18. "title":"<a href='http://www.baidu.com'>百度一下</a>",
    19. })
    20. })
    21. r.Run() // 启动server
    22. }

    在index.tmpl中使用定义好的safe模板函数:

    1. <!DOCTYPE html>
    2. <html lang="zh-CN">
    3. <head>
    4. <title>修改模板引擎的标识符</title>
    5. </head>
    6. <body>
    7. <div>{{ .title | safe }}</div>
    8. </body>
    9. </html>

    6.3、静态文件处理

    当我们渲染HTML文件中需要引入静态文件时,我们调用gin.Static方法即可。

    1. func main() {
    2. r := gin.Default()
    3. r.Static("/static", "./static") // 第一个参数是url路径,第二个参数是实际文件所在路径
    4. r.LoadHTMLGlob("templates/**/*")
    5. // ...
    6. r.Run(":8080")
    7. }

    7、中间件

    7.1、定义全局中间件

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

    1. // 定义中间件(统计请求耗时)
    2. func MiddleWare() gin.HandlerFunc{
    3. return func(c *gin.Context) {
    4. start := time.Now()
    5. c.Set("name","Negan") // 通过c.Set在请求上下文中设置值,后续处理函数能够取到该值
    6. c.Next() // 调用该请求剩余的部分
    7. // c.Abort() // 不调用该请求剩余的部分
    8. // 计算耗时
    9. cost := time.Since(start)
    10. log.Println(cost)
    11. }
    12. }

    7.2、注册中间件

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

    7.2.1、为全局路由注册
    1. func main() {
    2. r := gin.New() // 新建一个没有任何默认中间件的路由引擎
    3. r.Use(MiddleWare()) // 注册一个全局中间件
    4. r.GET("/", func(c *gin.Context) {
    5. name := c.MustGet("name").(string) // 取值,并自动捕获处理异常
    6. c.JSON(http.StatusOK,gin.H{
    7. "name":name,
    8. })
    9. })
    10. r.Run()
    11. }

    7.2.2、为某一个路由单独注册(可注册多个)
    1. func main() {
    2. r := gin.New() // 新建一个没有任何默认中间件的路由引擎
    3. r.GET("/index1", MiddleWare(), func(c *gin.Context) {
    4. name := c.MustGet("name").(string) // 取值,并自动处理异常
    5. c.JSON(http.StatusOK,gin.H{
    6. "name":name,
    7. })
    8. })
    9. r.Run()
    10. }

    7.2.3、为某一个方法注册中间件
    1. // 处理器函数
    2. func M1(c *gin.Context){
    3. c.JSON(http.StatusOK,gin.H{
    4. "msg":"OK",
    5. })
    6. }
    7. func main(){
    8. r := gin.New()
    9. r.User(M1,MiddleWare()) // m1处理器函数注册中间件
    10. r.GET("/m1",M1)
    11. r.Run()
    12. }

    7.2.4、为路由组注册中间件

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

    • 写法一: ```java user := r.Group(“/user”) user.Use(MiddleWare()) { user.GET(“/index1”, func(c *gin.Context) {
      1. name := c.MustGet("name").(string) // 取值,并自动处理异常
      2. c.JSON(http.StatusOK,gin.H{
      3. "name":name,
      4. })
      }) user.GET(“/index2”,func(c *gin.Context) {
      1. name := c.MustGet("name").(string) // 取值,并自动处理异常
      2. c.JSON(http.StatusOK,gin.H{
      3. "name":name,
      4. })
      }) }
    1. - 写法二:
    2. ```java
    3. user := r.Group("/user", MiddleWare())
    4. {
    5. user.GET("/index1", func(c *gin.Context) {
    6. name := c.MustGet("name").(string) // 取值,并自动处理异常
    7. c.JSON(http.StatusOK,gin.H{
    8. "name":name,
    9. })
    10. })
    11. user.GET("/index2",func(c *gin.Context) {
    12. name := c.MustGet("name").(string) // 取值,并自动处理异常
    13. c.JSON(http.StatusOK,gin.H{
    14. "name":name,
    15. })
    16. })
    17. }

    7.3、中间件注意事项

    gin.Default默认使用了Logger和Recovery中间件,其中:Logger中间件将日志写入gin.DefaultWriter,即配置了GIN_MODE=release。Recovery中间件会recover任何panic,如果有panic的话,会写入500响应码。如果不想使用上面两个默认中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

    8、会话控制

    8.1、Cookie

    HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出。cookie就是解决HTTP无状态的方案之一,cookie实际上就是服务器在浏览器上保存的一段信息,浏览器有了cookie之后,每次向服务器发送请求时都会将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求。

    8.1.1、Go操作cookie

    标准库net/http中定义了cookie,它代表一个出现在HTTP响应头中Set-Cookie的值,或者HTTP请求头中Cookie的值的HTTP cookie。

    1. type Cookie struct {
    2. Name string
    3. Value string
    4. Path string
    5. Domain string
    6. Expires time.Time
    7. RawExpires string
    8. // MaxAge=0表示未设置Max-Age属性
    9. // MaxAge<0表示立刻删除该cookie,等价于"Max-Age: 0"
    10. // MaxAge>0表示存在Max-Age属性,单位是秒
    11. MaxAge int
    12. Secure bool
    13. HttpOnly bool
    14. Raw string
    15. Unparsed []string // 未解析的“属性-值”对的原始文本
    16. }

    具体实现代码:

    1. // 创建两个cookie
    2. func setCookie(w http.ResponseWriter, r *http.Request){
    3. // 创建cookie1
    4. cookie1 := http.Cookie{
    5. Name: "user",
    6. Value: "admin",
    7. HttpOnly: true,
    8. MaxAge: 999,
    9. }
    10. // 创建cookie2
    11. cookie2 := http.Cookie{
    12. Name: "user1",
    13. Value: "admin1",
    14. HttpOnly:true,
    15. MaxAge:999,
    16. }
    17. // 将cookie发送给浏览器
    18. //w.Header().Set("Set-Cookie", cookie.String())
    19. //w.Header().Add("Set-Cookie",cookie.String())
    20. http.SetCookie(w, &cookie)
    21. }
    22. func getCookie(w http.ResponseWriter, r *http.Request){
    23. // 会将两个cookie全部获取
    24. //cookie := r.Header.Get("Cookie")
    25. //fmt.Fprintln(w,"获取的cookie是:", cookie)
    26. // 如果要得到一个cookie,可以直接调用Cookie方法
    27. cookie,_ := r.Cookie("user1")
    28. fmt.Fprintln(w,"获得的cookie是:",cookie) // user1=admin1
    29. }
    30. func main() {
    31. http.HandleFunc("/setCookie", setCookie)
    32. http.HandleFunc("/getCookie", getCookie)
    33. http.ListenAndServe(":8888",nil)
    34. }

    8.1.2、Gin中操作Cookie
    1. func main() {
    2. r := gin.New() // 新建一个没有任何默认中间件的路由引擎
    3. r.GET("/cookie", func(c *gin.Context) {
    4. cookie, err := c.Cookie("gin_cookie") // 获取cookie
    5. if err != nil {
    6. cookie = "NotSet"
    7. // 设置cookie
    8. c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
    9. }
    10. fmt.Println(cookie)
    11. })
    12. r.Run()
    13. }

    8.2、Session

    Cookie虽然在一定程度上解决了”保持状态”的需求,但是Cookie本身最大支持4096字节,以及Cookie保存在客户端,可能会被拦截窃取。这时就需要一种新的东西,能支持更多的字节,保存在客户端,有较高的安全性,这就是Session。我们将Session ID保存到Cookie中,服务器通过该Session ID就能找到与之对应的Session数据 。Session我们可以使用第三方库实现(基于Redis)

    1. package main
    2. import (
    3. "github.com/gin-contrib/sessions"
    4. "github.com/gin-contrib/sessions/redis"
    5. "github.com/gin-gonic/gin"
    6. )
    7. func main() {
    8. r := gin.Default()
    9. store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
    10. r.Use(sessions.Sessions("mysession", store))
    11. r.GET("/incr", func(c *gin.Context) {
    12. session := sessions.Default(c)
    13. var count int
    14. v := session.Get("count")
    15. if v == nil {
    16. count = 0
    17. } else {
    18. count = v.(int)
    19. count++
    20. }
    21. session.Set("count", count)
    22. session.Save()
    23. c.JSON(200, gin.H{"count": count})
    24. })
    25. r.Run(":8000")
    26. }

    8.3、JWT

    JWT全称JSON Web Token,是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAth2.0业务场景下。JWT就是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,经过签名后得到一个Token再发回给用户,用户后续请求只需要带上这个Token,服务端解密后就能获取该用户的相关信息了。

    8.3.1、生成和解析JWT

    我们在这里直接使用jwt-go这个库来实现我们生成和解析JWT的功能。

    • 定义需求

    我们根据自己的需求来来决定JWT中保存哪些数据,比如我们规定在JWT中存储username信息,那么我们就定义一个MyClaims结构体。

    1. // MyClaims 自定义声明结构体并内嵌jwt.StandardClaims
    2. // jwt.StandardClaims只包含了官方字段
    3. // 我们这里需要额外记录一个username字段,所以自定义结构体
    4. // 如果想要保存更多信息,都可以添加到这个结构体中
    5. type MyClaims struct {
    6. Username string `json:"username"`
    7. jwt.StandardClaims
    8. }
    9. const TokenExpireDuration = time.Hour * 2 // 设置过期时间为两小时
    10. var MySecret = []byte("永远不要高估自己") // 定义一个密钥
    • 生成JWT和解析JTW ```java // 生成JWT func GenToken(username string) (string, error) { t := MyClaims{
      1. username,
      2. jwt.StandardClaims{
      3. ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
      4. Issuer: "Negan", // 签发人
      5. },
      } // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, t) // 使用指定的secret签名并获得完整的编码后的字符串token fmt.Println(token.SignedString(MySecret)) return token.SignedString(MySecret) }

    // 解析JWT func ParseToken(tokenString string) (MyClaims, error) { // 解析token token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token jwt.Token) (interface{}, error) { return MySecret, nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { return claims, nil } return nil, errors.New(“invalid token”) }

    1. <a name="mhWSM"></a>
    2. ##### 8.3.2、Gin中使用JWT
    3. ```java
    4. // 定义用户结构体
    5. type UserInfo struct {
    6. Username string `json:"username" form:"username" binding:"required"`
    7. Password string `json:"password" form:"password" binding:"required"`
    8. }
    9. func authHandler(c *gin.Context) {
    10. // 获取用户发送的用户名以及密码
    11. user := UserInfo{}
    12. if err := c.ShouldBind(&user); err != nil {
    13. c.JSON(http.StatusOK, gin.H{
    14. "code": 404,
    15. "msg": "无效的参数",
    16. })
    17. return
    18. }
    19. // 检验用户名以及密码是否正确
    20. if user.Username == "root" && user.Password == "123456" {
    21. // 生成token
    22. tokenString, err := GenToken(user.Username)
    23. if err != nil{
    24. fmt.Println(err)
    25. }
    26. c.JSON(http.StatusOK, gin.H{
    27. "code": 200,
    28. "msg": "success",
    29. "data": gin.H{"token": tokenString},
    30. })
    31. return
    32. }
    33. c.JSON(http.StatusOK, gin.H{
    34. "code": 400,
    35. "msg": "鉴权失败",
    36. })
    37. return
    38. }
    39. func JWTAuthMiddleware() gin.HandlerFunc {
    40. return func(c *gin.Context) {
    41. authHeader := c.Request.Header.Get("Authorization")
    42. if authHeader == "" {
    43. c.JSON(http.StatusOK, gin.H{
    44. "code": 400,
    45. "msg": "请求头auth为空",
    46. })
    47. fmt.Println("请求头为空")
    48. c.Abort() // 终止下面代码
    49. return
    50. }
    51. // 按照空格分割
    52. parts := strings.SplitN(authHeader, " ", 2)
    53. fmt.Println(parts[0])
    54. if !(len(parts) == 2 && parts[0] == "Bearer") {
    55. c.JSON(http.StatusOK, gin.H{
    56. "code": 404,
    57. "msg": "请求头中auth格式有误",
    58. })
    59. c.Abort()
    60. return
    61. }
    62. // parts[1]是获取到的tokenString,使用我们之前定义解析函数解析
    63. msg, err := ParseToken(parts[1])
    64. if err != nil {
    65. c.JSON(http.StatusOK, gin.H{
    66. "code": 400,
    67. "msg": "无效的token",
    68. })
    69. c.Abort()
    70. return
    71. }
    72. // 将当前获取的username 保存到上下文中
    73. c.Set("username", msg.Username)
    74. fmt.Println(msg.Username)
    75. c.Next() // 后续处理函数会通过c.Get("username")来获取
    76. }
    77. }
    78. func main() {
    79. r := gin.Default()
    80. r.POST("/auth", authHandler) // 设置Token
    81. r.GET("/home",JWTAuthMiddleware(), func(c *gin.Context) {
    82. username := c.MustGet("username").(string)
    83. fmt.Println(username)
    84. c.JSON(http.StatusOK, gin.H{
    85. "code": 200,
    86. "msg": "success",
    87. "data": username,
    88. })
    89. })
    90. r.Run()
    91. }

    9、Gin中使用goroutine

    当我们在中间件或者handler中启动新的gouroutine时,不能使用原始上下文(c *gin.Context),必须使用其只读副本c.Copy()。如果不使用只读副本,则c的后续的操作不可控,造成并发不安全。

    10、运行多个服务

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

    1. package main
    2. import (
    3. "log"
    4. "net/http"
    5. "time"
    6. "github.com/gin-gonic/gin"
    7. "golang.org/x/sync/errgroup"
    8. )
    9. var (
    10. g errgroup.Group
    11. )
    12. func router01() http.Handler {
    13. e := gin.New()
    14. e.Use(gin.Recovery())
    15. e.GET("/", func(c *gin.Context) {
    16. c.JSON(
    17. http.StatusOK,
    18. gin.H{
    19. "code": http.StatusOK,
    20. "error": "Welcome server 01",
    21. },
    22. )
    23. })
    24. return e
    25. }
    26. func router02() http.Handler {
    27. e := gin.New()
    28. e.Use(gin.Recovery())
    29. e.GET("/", func(c *gin.Context) {
    30. c.JSON(
    31. http.StatusOK,
    32. gin.H{
    33. "code": http.StatusOK,
    34. "error": "Welcome server 02",
    35. },
    36. )
    37. })
    38. return e
    39. }
    40. func main() {
    41. server01 := &http.Server{
    42. Addr: ":8080",
    43. Handler: router01(),
    44. ReadTimeout: 5 * time.Second,
    45. WriteTimeout: 10 * time.Second,
    46. }
    47. server02 := &http.Server{
    48. Addr: ":8081",
    49. Handler: router02(),
    50. ReadTimeout: 5 * time.Second,
    51. WriteTimeout: 10 * time.Second,
    52. }
    53. // 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
    54. g.Go(func() error {
    55. return server01.ListenAndServe()
    56. })
    57. g.Go(func() error {
    58. return server02.ListenAndServe()
    59. })
    60. if err := g.Wait(); err != nil {
    61. log.Fatal(err)
    62. }
    63. }

    11、图片验证码

    1. import (
    2. "github.com/gin-gonic/gin"
    3. "github.com/mojocn/base64Captcha"
    4. "image/color"
    5. )
    6. type CaptchaResult struct{
    7. Id string `json:"id"`
    8. Base64lob string `json:"base_64_lob"`
    9. VertifyValue string `json:"code"`
    10. }
    11. // 生成图形验证码
    12. func GenerateCaptcha(c *gin.Context){
    13. var parameters = base64Captcha.ConfigCharacter{
    14. Height: 30,
    15. Width: 60,
    16. Mode: 3,
    17. ComplexOfNoiseDot: 0,
    18. ComplexOfNoiseText: 0,
    19. IsShowHollowLine: false,
    20. IsShowNoiseDot: false,
    21. IsShowNoiseText: false,
    22. IsShowSineLine: false,
    23. IsShowSlimeLine: false,
    24. IsUseSimpleFont: true,
    25. CaptchaLen: 4,
    26. BgColor: &color.RGBA{
    27. R: 3,
    28. G: 102,
    29. B: 214,
    30. A: 254,
    31. },
    32. }
    33. captchaId, captchaInterfaceInstance := base64Captcha.GenerateCaptcha("",parameters)
    34. base64blob := base64Captcha.CaptchaWriteToBase64Encoding(captchaInterfaceInstance)
    35. captchaResult := CaptchaResult{Id: captchaId,Base64lob: base64blob}
    36. Success(c, map[string]interface{}{
    37. "captcha_result": captchaResult,
    38. })
    39. }
    40. // 验证验证码
    41. func VertifyCaptcha(id string, value string)bool{
    42. return base64Captcha.VerifyCaptcha(id, value)
    43. }

    参考:https://www.cnblogs.com/huiyichanmian/p/14072124.html

    GoWeb服务器搭建

    参考:https://www.cnblogs.com/huiyichanmian/p/13798940.html

    FastDFS分布式文件服务器搭建以及Golang和Python调用

    参考:https://www.cnblogs.com/huiyichanmian/p/14096406.html

    Golang操作Json

    参考:https://www.cnblogs.com/huiyichanmian/p/12955230.html

    Go测试

    参考:https://www.cnblogs.com/huiyichanmian/p/12911741.html

    weinxin
    版权声明
    本站原创文章转载请注明文章出处及链接,谢谢合作!
    Go测试-《GO开发知识笔记》 编程

    Go测试-《GO开发知识笔记》

    单元测试测试函数测试函数的格式测试函数示例测试组子测试测试覆盖率基准测试基准测试函数格式基准测试示例性能比较函数重置时间并行测试Setup与TearDownTe
    评论:0   参与:  0