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 协议 ,转载请注明出处!