gin快速入门实战 | 青训营

142 阅读7分钟

Gin 是一个 Go(Golang) 编写的轻量级 http web 架,运行速度非常快。 感觉学完javaweb后在学goweb上手就很快,很多东西原理上是一样的,只是实现方式有所改变

安装gin

安装失败时配置go环境

  go env -w GO111MODULE=on
  go env -w GOPROXY=https://goproxy.io,direct

安装gin

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

创建gin项目

goland创建时,直接选择go modules即可,否则后面导包可能会出错

启动gin

 package main  
   
 import (  
    "github.com/gin-gonic/gin"  
    "net/http"
    )  
   
 func main() {  
    // 创建一个默认路由  
    r := gin.Default()  
    // 新建一个路由  
    r.GET("/", func(c *gin.Context) {  
       c.String(http.StatusOK, "搭建完成")  
    })  
    r.GET("/hello", func(c *gin.Context) {  
       c.String(http.StatusOK, "hello,gin")  
    })  
    // 启动web服务 默认在8080端口运行  
    r.Run(":8888") // 端口号8888  
 }

安装fresh

gin项目热加载 安装fresh

 go get github.com/pilu/fresh

在项目目录下运行fresh即可

gin返回json数据

使用map

 r.GET("/success", func(c *gin.Context) {  
    c.JSON(http.StatusOK,map[string] interface{}{  
       "code" : 200,  
       "msg" : "success",  
       "data" : "nil",  
    })  
 })

使用gin.H

 r.GET("/success2", func(c *gin.Context) {  
    c.JSON(http.StatusOK,gin.H{  
       "code" : 200,  
       "msg" : "success",  
       "data" : "gin H类型",  
    })  
 })

返回结构体类型

 r.GET("/success3", func(c *gin.Context) {  
    article := Article{  
       Tiltle: "test1",  
       Desc:   "hahah",  
       Author: "sunzy",  
    }  
    c.JSON(http.StatusOK,article)  
 })

响应jsonp请求

 // jsonp能将回调函数的内容返回  
 // http://127.0.0.1:8888/jsonp?callback=1111
 r.GET("/jsonp", func(c *gin.Context) {  
    article := Article{  
       Tiltle: "test1",  
       Desc:   "hahah",  
       Author: "sunzy",  
    }  
    c.JSONP(http.StatusOK,article)  
 })

![[Pasted image 20221207170728.png]]

渲染模板

首先创建文件夹保存html文件 路由加载所有的html

 // 路由加载模板
 r.LoadHTMLGlob("templates/*")  
 //渲染模板  
 r.GET("/html", func(c *gin.Context) {  
   
    c.HTML(http.StatusOK, "index.html", gin.H{  
       "title" : "我是后台数据",  // 使用gin.H可以向前端模板传值
    })  
 })
 ​

传值操作

get方法

 // get 传值  
 r.GET("/get", func(c *gin.Context) {  
    username := c.Query("username")  
    password := c.Query("password")  
    // 当值为空时 赋默认值
    sex := c.DefaultQuery("sex", "man")  
   
    c.String(http.StatusOK, username + password + sex)  
   
 })

post方法

 // post 传值  
 r.POST(    "/post", func(c *gin.Context) {  
    username := c.PostForm("username")  
    password := c.PostForm("password")  
    sex := c.DefaultPostForm("sex", "man")  
   
    c.String(http.StatusOK, username + password + sex)  
   
 })

将请求参数绑定到结构体

 // 请求参数绑定到结构体  
 r.GET("/getUser", func(c *gin.Context) {  
    user := &UserInfo{}  
    err := c.ShouldBind(user)  
    if err == nil{  
       c.JSON(http.StatusOK, user)  
    }else {  
       c.JSON(http.StatusOK, gin.H{  
          "code" : 200,  
          "msg" : err.Error(),  
       })  
    }  
 })

动态路由传值(Restful)

r.GET("/user/:uid", func(c *gin.Context) {  
   uid:= c.Param("uid")  
   c.JSON(http.StatusOK, gin.H{  
      "msg" : uid,  
   })  
})

路由分组

defaultRouter := r.Group("/")  // 关键点
{  
   defaultRouter.GET("/index", func(c *gin.Context) {  
      c.JSON(http.StatusOK, gin.H{  
         "msg": "首页",  
      })  
   })  
  
   defaultRouter.GET("/news", func(c *gin.Context) {  
      c.JSON(http.StatusOK, gin.H{  
         "msg": "新闻列表",  
      })  
   })  
  
   defaultRouter.GET("/users", func(c *gin.Context) {  
      c.JSON(http.StatusOK, gin.H{  
         "msg": "用户列表",  
      })  
   })  
}  
apiRouter := r.Group("/api")  
{  
   apiRouter.GET("/user", func(c *gin.Context) {  
      c.JSON(http.StatusOK, gin.H{  
         "msg": "api接口",  
      })  
   })  
  
   apiRouter.GET("/news", func(c *gin.Context) {  
      c.JSON(http.StatusOK, gin.H{  
         "msg": "api接口2",  
      })  
   })  
  
   apiRouter.GET("/admin", func(c *gin.Context) {  
      c.JSON(http.StatusOK, gin.H{  
         "msg": "api接口3",  
      })  
   })  
}  
  
  
adminRouter := r.Group("/admin")  
{  
   adminRouter.GET("/index", func(c *gin.Context) {  
      c.JSON(http.StatusOK, gin.H{  
         "msg": "admin后台",  
      })  
   })  
  
   adminRouter.GET("/login", func(c *gin.Context) {  
      c.JSON(http.StatusOK, gin.H{  
         "msg": "admin后台登录",  
      })  
   })  
}

路由抽离

将路由分组的过程封装成单独的函数即可

例如

package routers  
  
import (  
   "github.com/gin-gonic/gin"  
   "net/http")  
  
func DefaultRouter(r *gin.Engine){  
   defaultRouter := r.Group("/")  
   {  
      defaultRouter.GET("/index", func(c *gin.Context) {  
         c.JSON(http.StatusOK, gin.H{  
            "msg": "首页",  
         })  
      })  
  
      defaultRouter.GET("/news", func(c *gin.Context) {  
         c.JSON(http.StatusOK, gin.H{  
            "msg": "新闻列表",  
         })  
      })  
  
      defaultRouter.GET("/users", func(c *gin.Context) {  
         c.JSON(http.StatusOK, gin.H{  
            "msg": "用户列表",  
         })  
      })  
   }  
}

在main.go中调用

controller

抽离

就是将路由里的方法抽离出去 使用外部文件中创建的方法即可

首先创建controllers/admin文件夹 创建userController

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


// 创建对应的结构体 然后绑定对应的方法 可以简化开发步骤
type UserController struct {  
  
}  
  
func (con UserController) Index(c *gin.Context) {  
   c.JSON(http.StatusOK, gin.H{  
      "msg": "用户列表",  
   })  
}  
  
func (con UserController) Add(c *gin.Context) {  
   c.JSON(http.StatusOK, gin.H{  
      "msg": "添加user",  
   })  
}  

func (con UserController) Edit(c *gin.Context) {  
   c.JSON(http.StatusOK, gin.H{  
      "msg": "修改user",  
   })  
}

创建articleController

package admin  
  
import (  
   "github.com/gin-gonic/gin"  
   "net/http")  
  
type ArticleController struct {  
  
}  
  
  
func (con ArticleController) Add(c *gin.Context) {  
   c.JSON(http.StatusOK, gin.H{  
      "msg": "添加article",  
   })  
}  
  
  
func (con ArticleController) Edit(c *gin.Context) {  
   c.JSON(http.StatusOK, gin.H{  
      "msg": "修改article",  
   })  
}

在adminRouter中修改代码使用上面的Controller

package routers  
import (  
   "gindemo2/controllers/admin"  
   "github.com/gin-gonic/gin"   "net/http"
)  
  
func AdminRouter(r *gin.Engine){  
   adminRouter := r.Group("/admin")  
   {  
      adminRouter.GET("/index", func(c *gin.Context) {  
         c.JSON(http.StatusOK, gin.H{  
            "msg": "admin后台",  
         })  
      })  
      adminRouter.GET("/user", admin.UserController{}.Index)  
      adminRouter.GET("/user/add", admin.UserController{}.Add)  
      adminRouter.GET("/user/edit", admin.UserController{}.Edit)  
      adminRouter.GET("/article/add", admin.ArticleController{}.Add)  
      adminRouter.GET("/article/edit", admin.ArticleController{}.Edit)  
   }  
}

继承

创建baseController,这里可以写一些通用的处理函数

package admin  
  
import (  
   "github.com/gin-gonic/gin"  
   "net/http")  
  
type BaseController struct {  
  
}  
  
func (con BaseController) Success(c *gin.Context) {  
   c.JSON(http.StatusOK, gin.H{  
      "msg": "success",  
   })  
}  
  
func (con BaseController) Error(c *gin.Context) {  
   c.JSON(http.StatusOK, gin.H{  
      "msg": "error",  
   })  
}

userController继承baseController中方法

package admin  
  
import (  
   "github.com/gin-gonic/gin"  
   "net/http")  
  
type UserController struct {  
   BaseController  // 继承baseController
}

userController可以直接使用baseController中的方法

中间件

用法

![[Pasted image 20221207215236.png]] 如图gin.GET方法的第二个参数是可变参数类型,可以放多个回调函数,最后一个回调函数是controller,前面的则作为中间件,可作为拦截器、日志记录、执行时间统计 可直接

r.GET("/", func(context *gin.Context) {  
   fmt.Println("aaaaa")  // 中间件
}, func(c *gin.Context) {  
   c.String(http.StatusOK, "搭建完成")  
})

也可将函数封装后调用

func initMiddleWare(c *gin.Context){  
   fmt.Println("aaaa")  
}

func main(){
... 
r.GET("/hello", initMiddleWare,func(c *gin.Context) {  
   c.String(http.StatusOK, "hello,gin111")  
})
...
}

c.Next()

调用Next(),会执行中间件后的回调函数

func initMiddleWare(c *gin.Context){  
   fmt.Println("This is a middleware1...")  
   c.Next()  
   fmt.Println("This is a middleware2...")  
}


r.GET("/hello", initMiddleWare,func(c *gin.Context) {  
   fmt.Println("This is index...")  
   c.String(http.StatusOK, "hello,gin111")  
})

执行结果

22:5:29 app         | This is a middleware1...
22:5:29 app         | This is index...
22:5:29 app         | This is a middleware2...

c.Abort()

表示终止调用该请求的剩余处理程序,即会终止中间后的回调函数,但是该中间件中的处理继续完成

多个中间件的执行顺序

与SpringBoot中的拦截器的执行顺序一样 定义两个中间件

func initMiddleWare1(c *gin.Context){  
   fmt.Println("This is a middleware1-1...")  
   c.Next()  
   fmt.Println("This is a middleware1-2...")  
}  
  
func initMiddleWare2(c *gin.Context){  
   fmt.Println("This is a middleware2-1...")  
   c.Next()  
   fmt.Println("This is a middleware2-2...")  
}

在路由中添加中间件

r.GET("/hello", initMiddleWare1,initMiddleWare2, func(c *gin.Context) {  
   fmt.Println("This is index...")  
   c.String(http.StatusOK, "hello,gin111")  
})

执行顺序

22:13:17 app         | This is a middleware1-1...
22:13:17 app         | This is a middleware2-1...
This is index...
This is a middleware2-2...
This is a middleware1-2...

全局中间件

使用r.Use即可添加全局中间件,并且可以添加多个

r.Use(initMiddleWare)

路由分组添加全局中间件

使用Use函数

adminRouter := r.Group("/admin")  
adminRouter.Use(intMiddleWare)

或者直接添加

adminRouter := r.Group("/admin", intMiddleWare)  

中间件与控制器之间共享数据

使用Set 和 Get,只能在一个页面中共享数据 中间件中使用c.Set函数传递值

func InitMiddleWare(c *gin.Context){  
  
   //可以作为用户登录的拦截器  
   fmt.Println(c.Request.URL)  
  
   c.Set("username", "zhangsan")  
}

控制器中使用c.Get获取中间件传的值

func (con UserController) Index(c *gin.Context) {  
  
   username, exists := c.Get("username")  
   if exists{  
      fmt.Println(username)  
   }  
   c.JSON(http.StatusOK, gin.H{  
      "msg": "用户列表",  
   })  
}

![[Pasted image 20221207223132.png]]

Get返回的类型为空接口,使用时主要用类型断言

中间件使用goroutine

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

func InitMiddleWare(c *gin.Context){  
  
   //可以作为用户登录的拦截器  
   fmt.Println(c.Request.URL)  
  
   c.Set("username", "zhangsan")  
  
   // 定义一个gotoutine 统计日志  
   context:= c.Copy()  
   go func() {  
      time.Sleep(2 * time.Second)  
      fmt.Println("Done! in path :"  + context.Request.URL.Path )  
   }()  
}

model

可以在model 中定义一些公用的函数,这样可以在routers和controllers中共同使用 在model中定义的函数的名称首字母需要大写,但是在java中这种就叫做工具类啊,直接放在utils包即可,model不是应该跟数据库绑定的吗???有点不理解

package models  
  
import "time"  
  
//时间戳转日期  
func UnixToTime(timestamp int) string{  
   t := time.Unix(int64(timestamp), 0)  
   return t.Format("2006-01-02 15:04:05")  
}  
  
  
// 获取时间戳  
func GetUnix() int64{  
   return time.Now().Unix()  
}  
// 获取当前日期  
func GetDate() string{  
   templete := "2006-01-02 15:04:05"  
   return time.Now().Format(templete)  
}  
// 获取年月日  
func GetDay() string{  
   template := "20060102"  
   return time.Now().Format(template)  
}

Gin文件上传

单个文件上传

func (con UserController) Upload(c *gin.Context) {  
   username := c.PostForm("username")  
   file, err := c.FormFile("file")  
   if err != nil{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error:" + err.Error(),  
      })  
   }  
   fmt.Println(file.Filename)  
   fmt.Println(username)  
	// 第二个参数 为文件保存地址
   err = c.SaveUploadedFile(file, "./upload/" + file.Filename)  
   if err != nil{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error:" + err.Error(),  
      })  
   }  
   c.JSON(http.StatusOK, gin.H{  
      "msg": "file upload success",  
   })  
}

多个文件上传

方式一

采用单文件上传的方式处理多文件

func (con UserController) Uploads1(c *gin.Context) {  
   username := c.PostForm("username")  
  
   dst := "./upload/"  
   file1, err := c.FormFile("file1")  
   if err != nil{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error:" + err.Error(),  
      })  
   }  
   err = c.SaveUploadedFile(file1, dst + file1.Filename)  
   if err != nil{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error:" + err.Error(),  
      })  
   }  
  
   file2, err := c.FormFile("file2")  
   if err != nil{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error:" + err.Error(),  
      })  
   }  
   err = c.SaveUploadedFile(file2, dst + file2.Filename)  
   if err != nil{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error:" + err.Error(),  
      })  
   }  
   c.JSON(http.StatusOK, gin.H{  
      "username": username,  
      "msg": "file upload success",  
   })  
}

方式二

使用range遍历

func (con UserController) Uploads2(c *gin.Context) {  
   username := c.PostForm("username")  
   dst := "./upload/"  
  
   form, err := c.MultipartForm()  
   if err != nil{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error:" + err.Error(),  
      })  
   }  
   files := form.File["file[]"]  
  
   for _, file := range files{  
      err = c.SaveUploadedFile(file, dst + file.Filename)  
      if err != nil{  
         c.JSON(http.StatusBadRequest, gin.H{  
            "msg": "file upload error:" + err.Error(),  
         })  
      }  
   }  
  
   c.JSON(http.StatusOK, gin.H{  
      "username": username,  
      "msg": "file upload success",  
   })  
}

注意提交表单时的参数类型为file

按日期存储图片

对上传的文件进行后缀名检查,并对图片进行重命名

func (con UserController) Upload(c *gin.Context) {  
   username := c.PostForm("username")  
   dst := "./upload/"  
   file, err := c.FormFile("file")  
   if err != nil{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error:" + err.Error(),  
      })  
   }  
   // 判断文件后缀名是否为.jpg,.png,.gif,.jpeg  
   ext := path.Ext(file.Filename)  
   allowExt := map[string] bool{  
      ".jpg" : true,  
      ".png" : true,  
      ".gif" : true,  
      ".jpeg" : true,  
   }  
   if _, ok := allowExt[ext]; !ok{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error: extname do not allow",  
      })  
      return  
  
   }  
   // 以时间为名称保存文件  
   day := models.GetDay()  
  
   dir := dst + day  
  
   err = os.MkdirAll(dir, 0666)  
   if err!= nil{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error: mkdir failed!",  
      })  
      return  
  
   }  
   unix:= models.GetUnix()  
  
   filename := strconv.FormatInt(unix, 10) + ext  
  
   dts := path.Join(dir, filename)  
   err = c.SaveUploadedFile(file,dts)  
   if err != nil{  
      c.JSON(http.StatusBadRequest, gin.H{  
         "msg": "file upload error:" + err.Error(),  
      })  
      return  
   }  
   c.JSON(http.StatusOK, gin.H{  
      "username" : username,  
      "msg": "file upload success",  
   })  
}

gin 设置Cookie

c.SetCookie的参数说明 ![[Pasted image 20221208142533.png]] ![[Pasted image 20221208142611.png]] 设置cookie

c.SetCookie("username", "zhangsan", 3600, "/", "localhost", false, false)

获取Cookie

// 获取cookie  
cookie, err := c.Cookie("username")

Cookie过期时间说明

// MaxAge=0 means no 'Max-Age' attribute specified.  
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'  
// MaxAge>0 means Max-Age attribute present and given in seconds

多个二级域名共享cookie

c.SetCookie("username", "zhangsan", 3600, "/", ".baidu.com", false, false)

即可 设置完成后,news.baidu.com 和pan.baidu.com 之间能够共享cookie

gin设置session

github.com/gin-contrib/sessions 项目地址 github.com/gin-contrib…

导入包

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

设置session中间件

//store := cookie.NewStore([]byte("secret"))  
// 配置session中间件 store是存储引擎 也可以配置成其他的引擎  
// 基于redis的存储引擎  
store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))  
r.Use(sessions.Sessions("mysession", store))

设置session

// 设置session  
session := sessions.Default(c)  
session.Options(sessions.Options{  
   MaxAge: 3600, //设置session的过期时间 单位是秒
})
session.Set("username", "lisi")  
session.Save()  // 必须调用Save

Redis 作为存储引擎

导入包

"github.com/gin-contrib/sessions"  
"github.com/gin-contrib/sessions/redis"

配置redis为存储引擎

store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))  
r.Use(sessions.Sessions("mysession", store))

配置成功后,服务器端的session就会存储到redis中