gin

本文最后更新于:1 年前

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[]
![[Pasted image 20221208135337.png]]

按日期存储图片

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

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
项目地址
https://github.com/gin-contrib/sessions

导入包

"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中


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

 目录

Copyright © 2020 my blog
载入天数... 载入时分秒...