Go-Gin框架

一、Gin介绍

Gin是一个用Go编写的HTTPweb框架。它是一个类似于martini但拥有更好性能的API框架, 优于httprouter,速度提高了近 40 倍。 点击此处访问Gin官方中文文档。

二、安装

1、安装Gin

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

2、代码中引入

import "github.com/gin-gonic/gin"

3、简单示例,验证

新建文件main.go,内容如下:

package main

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

func main() {
    // 1.创建路由
   r := gin.Default()
   // 2.绑定路由规则,执行的函数
   // gin.Context,封装了request和response
   r.GET("/", func(c *gin.Context) {
      c.String(http.StatusOK, "hello World!")
   })
   // 3.监听端口,默认在8080
   // Run("")里面不指定端口号默认为8080
   r.Run(":8000")
}

运行后访问: http://localhost:8000/

三、渲染前端与配置跨域


1、渲染html模板


Gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据,本质上就是字符串替换。LoadHTMLGlob()方法可以加载模板文件(参数为待渲染的html模板文件,如果参数为相对路径则为运行路径的相对路径)。

a、 渲染单个文件

r.LoadHTMLGlob("web/index.html")


b、 渲染文件夹下的所有文件

r.LoadHTMLGlob("web/*")


c、 渲染文件夹下的所有html后缀的文件
比如:

r.LoadHTMLGlob("web/*.html")



2、定义模板分割

r.Delims("<<<", ">>>")

第一个参数为:模板标签开始标记
第二个参数为:模板标签结束标记

3、渲染静态文件和目录


如果你需要引入静态文件需要定义一个静态文件目录

r.Static("/assets", "./static/assets")


若assets的目录结构为


可以根据 http://localhost:8000/assets/img/home.jpg 访问指定资源
​​​​​​​

 

 如果r为路由组,则需要在assets前拼接路由组的路径包括其前缀

4、重定向

// 重定向两种默认应支持的首页访问方式
router.GET("/", func(c *gin.Context) {
	//重定向到/index.html
	c.Redirect(302, "/index.html")
})
router.GET("/index", func(c *gin.Context) {
	//重定向到/index.html
	c.Redirect(302, "/index.html")
})
router.GET("/index.html", func(c *gin.Context) {
	//返回渲染的html模板中的index.html
	c.HTML(http.StatusOK, "index.html", gin.H{
		"baseUrl": "http://" + host,
	})
})

5、配置跨域

Next()

r.Use(Next())

允许跨域

// 允许跨域
func Next() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Headers", "Access-Control-Allow-Headers,Authorization,User-Agent, Keep-Alive, Content-Type, X-Requested-With,X-CSRF-Token,AccessToken,Token")
		c.Header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, OPTIONS")
		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
		c.Header("Access-Control-Allow-Credentials", "true")

		// 放行所有OPTIONS方法
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusAccepted)
		}
		c.Next()
	}
}

6、响应格式        c.String() c.JSON() c.JSONP() c.XML() c.HTML()

(1)返回一个字符串

r.GET("/news", func(c *gin.Context) {
    aid := c.Query("aid")
    c.String(200, "aid=%s", aid)
})


(2)返回一个 JSON 数据
   

//方法一:自己拼接JSON
    r.GET("/json", func(c *gin.Context) {
        //返回json数据,使用 map[string]interface
        //c.JSON(返回的状态码, 任意类型的数据(如:map,struct,...)
        c.JSON(200, map[string]interface{}{
            "success": true,
            "msg":     "你好",
        })
    })
    //方法二:gin中的H函数
    r.GET("/json2", func(c *gin.Context) {
        //返回json数据,使用gin中的H函数, gin.H 是 map[string]interface{}的缩写
        c.JSON(200, gin.H{
            "success": true,
            "msg":     "你好gin",
        })
    })
    //方法三:使用结构体
    r.GET("/json3", func(c *gin.Context) {
        //实例化一个结构体
        a := &Article{
            Title:   "标题",
            Desc:    "说明",
            Content: "内容",
        }
        c.JSON(200, a)
    })

JSOPN: jsonp请求 主要用来解决跨域问题
 

  //jsonp请求 主要用来解决跨域问题
    //http://127.0.0.1:8080/jsonp?callback=call
    //call({"title":"标题-jsonp","desc":"说明-jsonp","content":"内容-jsonp"});
    r.GET("/jsonp", func(c *gin.Context) {
        //实例化一个结构体
        a := &Article{
            Title:   "标题-jsonp",
            Desc:    "说明-jsonp",
            Content: "内容-jsonp",
        }
        c.JSONP(200, a)
    })


(3)返回 XML 数据
   

//方法一:使用gin.H返回
    r.GET("/xml", func(c *gin.Context) {
        c.XML(http.StatusOK, gin.H{
            "success": true,
            "msg":     "成功xml",
        })
    })
    //方法二:使用结构体
    r.GET("/xmlStruct", func(c *gin.Context) {
        //实例化一个结构体
        a := &Article{
            Title:   "标题-xmlStruct",
            Desc:    "说明-xmlStruct",
            Content: "内容-xmlStruct",
        }
        c.XML(200, a)
    })

(4)返回HTML数据

     //初始化路由
     r := gin.Default()
     //加载templates文件中所有模板文件,以便后续c.HTML()渲染文件时使用
     r.LoadHTMLGlob("templates/*")
     r.GET("/news", func(c *gin.Context) {
        //使用模板文件渲染HTML文件
        //前提: r.LoadHTMLGlob("templates/*")
        //HTML(状态码, 要渲染的文件名, 加载的参数)
        c.HTML(http.StatusOK, "news.html", gin.H{
            "title": "我是一个news",
        })
    })


完整代码案例如下:

package main
 
import (
    "github.com/gin-gonic/gin"
    "net/http"
)
 
type Article struct {
    Title   string `json:"title"`
    Desc    string `json:"desc"`
    Content string `json:"content"`
}
 
func main() {
    //初始化路由
    r := gin.Default()
    //加载templates文件中所有模板文件,以便后续c.HTML()渲染文件时使用
    r.LoadHTMLGlob("templates/*")
    //配置路由
    r.GET("/", func(c *gin.Context) {
        c.String(200, "首页")
    })
    r.GET("/json", func(c *gin.Context) {
        //返回json数据,使用 map[string]interface
        //c.JSON(返回的状态码, 任意类型的数据(如:map,struct,...)
        c.JSON(200, map[string]interface{}{
            "success": true,
            "msg":     "你好",
        })
    })
    r.GET("/json2", func(c *gin.Context) {
        //返回json数据,使用gin中的H函数
        c.JSON(200, gin.H{
            "success": true,
            "msg":     "你好gin",
        })
    })
    r.GET("/json3", func(c *gin.Context) {
        //实例化一个结构体
        a := &Article{
            Title:   "标题",
            Desc:    "说明",
            Content: "内容",
        }
        c.JSON(200, a)
    })
    //jsonp请求 主要用来解决跨域问题
    //http://127.0.0.1:8080/jsonp?callback=call
    //call({"title":"标题-jsonp","desc":"说明-jsonp","content":"内容-jsonp"});
    r.GET("/jsonp", func(c *gin.Context) {
        //实例化一个结构体
        a := &Article{
            Title:   "标题-jsonp",
            Desc:    "说明-jsonp",
            Content: "内容-jsonp",
        }
        c.JSONP(200, a)
    })
    r.GET("/xml", func(c *gin.Context) {
        c.XML(http.StatusOK, gin.H{
            "success": true,
            "msg":     "成功xml",
        })
    })
    r.GET("/news", func(c *gin.Context) {
        //使用模板文件渲染HTML文件
        //前提: r.LoadHTMLGlob("templates/*")
        //HTML(状态码, 要渲染的文件名, 加载的参数)
        c.HTML(http.StatusOK, "news.html", gin.H{
            "title": "我是一个news",
        })
    })
    r.GET("/goods", func(c *gin.Context) {
        //使用模板文件渲染HTML文件
        //前提: r.LoadHTMLGlob("templates/*")
        //HTML(状态码, 要渲染的文件名, 加载的参数)
        c.HTML(http.StatusOK, "goods.html", gin.H{
            "title": "我是一个goods",
            "price": 12.99,
        })
    })
    r.Run() // 启动一个web服务
}

四、路由相关

1、创建路由组

// 创建路由组
// 根据需要,可以为这种多级的路由组:r.Group("/v1/user")
userApi:= r.Group("/user")

// 创建用户
// 匹配POST请求的 /user
userApi.POST("", userCreate)

// 修改用户
// 匹配PUT请求的 /user/1 但不会匹配 /user/ 或者 /user
userApi.PUT("/:id", userUpdate)

// 获取用户
// 匹配GET请求的 /user/1 但不会匹配 /user/ 或者 /user
userApi.GET("/:id", userGet)

// 查询用户
// 匹配GET请求的 /user/list
userApi.GET("/list", userQuery)

// 删除用户
// 匹配DELETE请求的 /user/1 但不会匹配 /user/ 或者 /user
userApi.DELETE("/:id", userDelete)

Restful风格的API

2、获取路由参数

a、api参数

通过Context的Param方法来获取api参数
 

userApi.GET("/:id/:name", userGet)

第一个参数:获取url路径参数id和name的值

第二个参数:userGet函数

func userGet(ctx *gin.Context) {
	//api参数可以为单个或者多个也可以拼接多级

	//ctx.Param()函数获取时参数需要与api中的名称一致才能获取到

	id := ctx.Param("id")
	name:= ctx.Param("name")

	ctx.JSON(http.StatusOK, gin.H{"data": id,"name":name})

	return
}

http://localhost:8000/user/1/admin

b、url参数

通过Context的Query方法与DefaultQuery方法来获取url参数

userApi.GET("/list", userQuery)

userQuery方法

func userQuery(ctx *gin.Context) {
	//获取
	id := ctx.Query("id")
	//获取,第二个参数为获取为空的默认值,如果参数不存在则放回第二个参数
	name := ctx.DefaultQuery("name", "user")
	ctx.JSON(http.StatusOK, gin.H{"data": id, "name": name})
	return
}

备注:默认值为当客户端没有在请求中带这个参数,服务端将取name默认值为”user“

可以根据 http://localhost:8000/user/list 后面拼接查询参数,访问此接口

 客户端没有有传参数,则取默认值

 客户端有传参数,则不取默认值

c、表单参数


表单参数测试与观察请求效果需要安装postman
通过Context的PostForm方法来获取表单参数

userApi.POST("", userCreate)

userCreate函数为

func userCreate(ctx *gin.Context) {
    id := ctx.PostForm("id")
    name := ctx.PostForm("name")
    ctx.JSON(http.StatusOK, gin.H{"data": id, "name": name})
    return
}



如图,访问此接口

d、json参数

json参数测试与观察请求效果需要安装postman
通过Context的GetRawData或者ShouldBindJSON方法来获取表单参数

userApi.PUT("/:id", userUpdate)

userUpdate函数为


GetRawData方法

func userUpdate(ctx *gin.Context) {
	
	b, err := ctx.GetRawData() // 从ctx.Request.Body读取请求数据
    if err !=nil{
        fmt.print(err)
    }
	// 定义map或结构体
	var m map[string]interface{}
	// 反序列化
	_ = json.Unmarshal(b, &m)
	ctx.JSON(http.StatusOK, gin.H{"data": m["id"], "name": m["name"]})
	return
}

ShouldBindJSON方法

// 先定义结构
type User struct {
	Id   string `form:"id" json:"id" binding:"required"`
	Name string `form:"name" json:"name" binding:"required"`
}

// 函数实现
func userUpdate(ctx *gin.Context) {
	var user User
	if err := ctx.ShouldBindJSON(&user); err == nil {
		ctx.JSON(http.StatusOK, gin.H{"data": user.Id, "name": user.Name})
		return
	} else {
		ctx.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
}

e、参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中form表单、JSON、XML等参数到结构体中。


代码示例:


定义要获取的结构

type User struct {
	Id   string `form:"id" json:"id" binding:"required"`
	Name string `form:"name" json:"name" binding:"required"`
}

userCreate函数

func userCreate(ctx *gin.Context) {
    // 实例化结构体对象
	var user User

	if err := ctx.ShouldBind(&user); err == nil {
        // 响应体 json 格式
		ctx.JSON(http.StatusOK, gin.H{"data": user.Id, "name": user.Name})
		return
	}else{
		ctx.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
}

userUpdate函数

func userUpdate(ctx *gin.Context) {
    // 实例化结构体对象
	var user User
	if err := ctx.ShouldBind(&user); err == nil {
		ctx.JSON(http.StatusOK, gin.H{"data": user.Id, "name": user.Name})
		return
	}else{
		ctx.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
}

3、上传文件

一般都是post请求表单传参

userApi.POST("/upload", userUpload)

userUpload函数

// 先定义结构
type FileUpload struct {
	File *multipart.FileHeader `form:"file"`
	Type string                `form:"type"`
}
// 函数实现
func userUpload(ctx *gin.Context) {
	var fileUpload FileUpload
// 
	if err := ctx.ShouldBind(&fileUpload); err == nil {
		//获取运行路径
		ex, err := os.Executable()
        //
		if err != nil {
			ctx.JSON(http.StatusOK, gin.H{"err": err.Error()})
			return
		}
		//定义接收文件的存放地址
		path := filepath.Dir(ex) + string(os.PathSeparator) + fileUpload.File.Filename
		//接收文件并保存到指定path
		err = ctx.SaveUploadedFile(fileUpload.File, path)
		ctx.JSON(http.StatusOK, gin.H{"data": path, "type": fileUpload.Type})
		return
	} else {
		ctx.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
}

五、路由分组分文件

使用Gin框架写Go项目的时候,如果把路由都写在一个.go文件中,随着路由的数量的增加,会导致路由文件显得异常臃肿。

此时需要对路由进行分组并且分文件管理,更有利于代码的组织和维护

使用Gin框架写Go项目的时候,如果把路由都写在一个.go文件中,随着路由的数量的增加,会导致路由文件显得异常臃肿。

此时需要对路由进行分组并且分文件管理,更有利于代码的组织和维护

路由分组的实现

原始未分组的文件
func main() {
	router := gin.Default()
	router.POST("/one/a", OneAFunc)
	router.POST("/one/b", OneBFunc)
	router.POST("/one/c", OneCFunc)
	router.POST("/one/d", OneDFunc)
	router.POST("/one/e", OneEFunc)
	router.POST("/one/f", OneFFunc)
	router.POST("/one/g", OneGFunc)
	router.POST("/one/h", OneHFunc)
	router.POST("/one/i", OneIFunc)
	/*
		省略n条路由
	*/
	router.POST("/one/x", OneXFunc)
	router.POST("/one/y", OneYFunc)
	router.POST("/one/z", OneZFunc)

	router.POST("/two/a", TwoAFunc)
	router.POST("/two/b", TwoBFunc)
	router.POST("/two/c", TwoCFunc)
	router.POST("/two/d", TwoDFunc)
	router.POST("/two/e", TwoEFunc)
	router.POST("/two/f", TwoFFunc)
	router.POST("/two/g", TwoGFunc)
	router.POST("/two/h", TwoHFunc)
	router.POST("/two/i", TwoIFunc)
	/*
		省略n条路由
	*/
	router.POST("/two/x", TwoXFunc)
	router.POST("/two/y", TwoYFunc)
	router.POST("/two/z", TwoZFunc)
	router.Run(":8080")
}
路由逻辑分组

我们首先创建了一个路由组 one,它的前缀为 /one。

然后,使用了 POST 方法在路由组 one 中定义了多个路由处理函数 oneAFunc到oneZFunc,它们分别处理 /two/a到 /two/z 路由的 HTTP POST 请求

请求路径

/one/a/

/tow/a/

	// 路由分组第一组
	one := router.Group("/one")
	{
		one.POST("/a", OneAFunc)
		one.POST("/b", OneBFunc)
		one.POST("/c", OneCFunc)
		one.POST("/d", OneDFunc)
		one.POST("/e", OneEFunc)
		one.POST("/f", OneFFunc)
		one.POST("/g", OneGFunc)
		one.POST("/h", OneHFunc)
		one.POST("/i", OneIFunc)
		/*
			省略n条路由
		*/
		one.POST("/x", OneXFunc)
		one.POST("/y", OneYFunc)
		one.POST("/z", OneZFunc)
	}
	//路由分组第二组
	two := router.Group("/two")
	{
		two.POST("/a", twoAFunc)
		two.POST("/b", twoBFunc)
		two.POST("/c", twoCFunc)
		two.POST("/d", twoDFunc)
		two.POST("/e", twoEFunc)
		two.POST("/f", twoFFunc)
		two.POST("/g", twoGFunc)
		two.POST("/h", twoHFunc)
		two.POST("/i", twoIFunc)
		/*
			省略n条路由
		*/
		two.POST("/x", twoXFunc)
		two.POST("/y", twoYFunc)
		two.POST("/z", twoZFunc)
	}

分组后的go代码,虽然路由都实现路由分组,路由的逻辑显得清晰,但是路由文件还是集中在一个文件中,文件还是大。

路由分组后并分文件管理

文件结构

main.go
handlers/
├── one_handlers.go
└── two_handlers.go

main.go

import (
	"github.com/gin-gonic/gin"
	"your_project/routers"
)

func main() {
    // 创建路由
	router := gin.Default()
    //
    routers.SetupRouter(router)
	// 路由分组第一组
	routers.TwoRoutersInit(router)
	// 路由分组第二组
	routers.OneRoutersInit(router)
    // 启动服务
	router.Run(":8080")
}

TwoRoutersInit() 和 OneRoutersInit()对应下面两个文件:

one_handlers.go

package routers

import "github.com/gin-gonic/gin"

func OneRoutersInit(engine *gin.Engine) {
	one := engine.Group("/one")
	{
		one.POST("/a", OneAFunc)
		one.POST("/b", OneBFunc)
		one.POST("/c", OneCFunc)
		one.POST("/d", OneDFunc)
		one.POST("/e", OneEFunc)
		one.POST("/f", OneFFunc)
		/*
			省略n条路由
		*/
		one.POST("/x", OneXFunc)
		one.POST("/y", OneYFunc)
		one.POST("/z", OneZFunc)
	}
}

func OneAFunc(context *gin.Context) {

}
/*
	省略N多方法
*/
func OneZFunc(context *gin.Context) {

}

two_handlers.go

package routers

import "github.com/gin-gonic/gin"

func TwoRoutersInit(engine *gin.Engine) {
	two := engine.Group("/two")
	{
		two.POST("/a", twoAFunc)
		two.POST("/b", twoBFunc)
		two.POST("/c", twoCFunc)
		two.POST("/d", twoDFunc)
		two.POST("/e", twoEFunc)
		two.POST("/f", twoFFunc)
		two.POST("/g", twoGFunc)
		two.POST("/h", twoHFunc)
		two.POST("/i", twoIFunc)
		/*
			省略n条路由
		*/
		two.POST("/x", twoXFunc)
		two.POST("/y", twoYFunc)
		two.POST("/z", twoZFunc)
	}
}

func twoAFunc(context *gin.Context) {

}
/*
省略n多方法
*/

func twoZFunc(context *gin.Context) {

}

备注:每个路由都要放在{}中,每个路由对应的方法入参固定context *gin.Context  其中context风状态request和response

官方示例:

main.go

// main.go
package main

import (
	"github.com/gin-gonic/gin"
	"your_project/routers"
)

func main() {
	r := gin.Default()

	routers.SetupRouter(r)

	r.Run() // listen and serve on 0.0.0.0:8080
}

routers/router.go

package routers

import (
	"github.com/gin-gonic/gin"
)

func SetupRouter(r *gin.Engine) {
	v1 := r.Group("/v1")
	{
		v1.GET("/hello", func(c *gin.Context) {
			c.JSON(200, gin.H{
				"message": "hello",
			})
		})
	}

	v2 := r.Group("/v2")
	{
		v2.GET("/hello", func(c *gin.Context) {
			c.JSON(200, gin.H{
				"message": "hello v2",
			})
		})
	}
}

六、中间件

1、统一注册中间件

对全局路由或者已注册的路由组统一注册中间件

r.Use(CheckToken())

2、单独注册中间件

userApi.POST("/upload",CheckToken(),userUpload)

3、中间件函数实现

func CheckToken() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 验证不通过直接跳出
		//c.JSON(http.StatusBadRequest, gin.H{"msg": "need token"})
		//c.Abort()
		//return
		// 验证通过继续
		c.Next()
	}
}

中间件场景

Go Gin JWT

https://blog.csdn.net/u013302168/article/details/132178429

七、vscode中如何使用go module导入包

go版本管理使用go module

(一)假设我们现在有moduleA和moduleB两个包


其中moduleA包中会导入moduleB包并使用它的New方法。
(注意:函数名称大写开头才能被引用)

moduleB/moduleB.go内容如下:

package moduleB

import "fmt"

func New(){
	fmt.Println("moduleB.New")
}

1.分两种情况讨论:

(1)在同一个项目下

注意:在一个项目(project)下我们是可以定义多个包(package)的。

目录结构如下:

在这里插入图片描述

接着创建moduleA的mod,在集成终端中打开,并在命令行输入

go mod init moduleA

 

 

执行后,会在项目根目录下生成一个mod.go文件,执行go mod tidy 执行安装依赖 。

moduleA的go.mod内容如下:

module moduleA

go 1.17

moudleA的main.go内容如下:

package main

import (
    "fmt"
    "moduleA/moduleB"
)

func main() {
    moduleB.New()
    fmt.Println("main")
}
(2)在不同项目下执行

到这里完成了第一步,现在来到的第二步;

首先,将moduleB的代码拉出外面来,我们来实现不同项目下的包的引用

此时的moduleB的包(文件夹)里没有mod文件,所以需要自己mod init 。

打开终端,在命令行输入

go mod init moduleB

此时moduleB的go.mod应该为:

module moduleA

go 1.17

require "moduleB" v0.0.0
replace "moduleB" => "../moduleB"


moduleB中的moduleB.go文件不动,和上面的一样


因为这两个包不在同一个项目路径下,你想要导入本地包,并且这些包也没有发布到远程的github或其他代码仓库地址。

这个时候我们就需要在go.mod文件中使用replace指令。

在调用方也就是moduleA/go.mod中按如下方式指定使用相对路径来寻找moduleB这个包。此时moduleA的go.mod文件应该为:

module moduleA

go 1.17

require "moduleB" v0.0.0
replace "moduleB" => "../moduleB"


moduleA的main.go文件应该为:

package main

import (
    "fmt"
    "moduleB"
)func main() {
    moduleB.New()
    fmt.Println("main")
}


 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liyinchi1988

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值