Go Web开源框架之Gin(2)

回顾

上一次,我们介绍了Gin框架的基本使用,包括路由的使用,html模板的渲染等。

静态文件服务

当我们渲染的 HTML 文件中引用了静态文件时,我们需要配置静态 web 服务。
r.Static("/static", "./static"), 前面的 /static 表示路由;后面的./static 表示路径。

main.go

//配置静态资源, 浏览器访问链接为 http://localhost:8080/static/css/base.css
r.Static("/static", "./static")

静态文件文件css引入:

<link rel="stylesheet" href="/static/css/base.css">

路由详解

路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等) 组成的,涉及到应用如何响应客户端对某个网站节点的访问。

前面章节我们给大家介绍了路由基础以及路由配置,这里我们详细给大家讲讲路由传值、路由返回值。

GET POST以及获取GET POST传值

GET 请求传值

// GET /?name=john&age=20&sex=male&address=shanghai
r.GET("/", func(c *gin.Context) {
	name := c.Query("name")
	age := c.Query("age")
	sex := c.Query("sex")
	address := c.DefaultQuery("address", "shanghai")
	c.JSON(200, gin.H{
		"name":    name,
		"age":     age,
		"sex":     sex,
		"address": address,
	})
})

动态路由传值

// xxxxxxxx/hello/ypb
r.GET("/hello/:name", func(c *gin.Context) {
	name := c.Param("name")
	c.String(200, "hello %s", name)
})

POST请求传值 获取表单数据

首先我们定义一个 html 页面。

{{define "templates/index.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/doAddUser1" method="post">
    用户名:<input type="text" name="username"> <br>
    密码:<input type="password" name="password"> <br>
<!--    年龄:<input type="text" name="age"> <br>-->
    <input type="submit" value="login">
</form>
<br>
</body>
</html>
{{end}}

通过 c.PostForm 接受表单传递来的数据。

r.POST("/doAddUser1", func(c *gin.Context) {
	username := c.PostForm("username")
	password := c.PostForm("password")
	age := c.DefaultPostForm("age", "18")
	c.JSON(200, gin.H{
		"username": username,
		"password": password,
		"age":      age,
	})
})

获取 GET传递的数据绑定到结构体

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

示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取 JSON、form 表单和 QueryString 类型的数据,并把值绑定到指定的结构体对象。

main.go

// 定义UserInfo2结构体
type UserInfo1 struct {
	Name string `form:"name" json:"name" binding:"required"`
	Age  int    `form:"age" json:"age" binding:"required"`
}
// 将数据绑定到UserInfo结构体
// http://localhost:8080/getUser?name=ypb&age=123456
r.GET("/getUser", func(c *gin.Context) {
	user := &UserInfo1{}
	if err := c.ShouldBind(&user); err != nil {
		c.JSON(200, gin.H{
			"error": err.Error(),
		})
	} else {
		fmt.Printf("user: %+v\n", user)
		c.JSON(200, user)
	}
})

返回数据

{"name":"ypb","age":123456}

获取 POST 传递的数据绑定到结构体

模板代码

{{define "templates/index.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/doAddUser2" method="post">
    用户名:<input type="text" name="username"> <br>
    密码:<input type="password" name="password"> <br>
<!--    年龄:<input type="text" name="age"> <br>-->
    <input type="submit" value="login">
</form>
</body>
</html>
{{end}}

main.go

// 获取模板页面
r.GET("/login", func(c *gin.Context) {
		c.HTML(200, "templates/index.html", gin.H{})
	})
// 绑定
r.POST("/doAddUser2", func(c *gin.Context) {
		user := &UserInfo2{}
		if err := c.ShouldBind(&user); err != nil {
			c.JSON(200, gin.H{
				"error": err.Error(),
			})
		} else {
			fmt.Printf("user: %+v\n", user)
			c.JSON(200, user)
		}
	})

返回数据

{"username":"yxc","password":"123456"}

获取POSTXML数据

在 API 的开发中,我们经常会用到 JSON 或 XML 来作为数据交互的格式,这个时候我们 可以在 gin 中使用 c.GetRawData() 获取数据。

定义数据结构体

type Article struct {
	Title   string `xml:"title" binding:"required"`
	Content string `xml:"content" binding:"required"`
}
// 获取POST xml请求的数据
r.POST("/getUserXml", func(c *gin.Context) {
	article := &Article{}

	xmlSliceData, _ := c.GetRawData()

	if err := xml.Unmarshal(xmlSliceData, &article); err == nil {
		c.JSON(200, article)
	} else {
		c.JSON(400, gin.H{"err": err.Error()})
	}
})

XML 请求:

<?xml version="1.0" encoding="UTF-8"?> <article>
<content type="string">我是张三</content>
<title type="string">张三</title> </article>

返回数据:

{
    "Title": "张三",
    "Content": "我是张三"
}

路由分组

路由分组就是将具有相似功能或路径的路由集合在一起,形成一个路由组。

func main() {
	router := gin.Default()
	// 简单的路由组: v1
	v1 := router.Group("/v1") {
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint) 
		v1.POST("/read", readEndpoint)
	}
	// 简单的路由组: v2
	v2 := router.Group("/v2") {
		v2.POST("/login", loginEndpoint) 
		v2.POST("/submit", submitEndpoint) 
		v2.POST("/read", readEndpoint)
	}
	router.Run(":8080") 
}

路由文件分组

核心思想是将相关的路由定义放在同一个文件或文件夹中,以便于维护和管理。

新建 router文件夹,在此文件夹下面新建adminRoutes.go, apiRoutes.go 和 defaultRoutes.go。

adminRoutes.go

// AdminRoutersInit  管理后台路由
func AdminRoutersInit(r *gin.Engine) {
	adminRouters := r.Group("/admin")
	{
		adminRouters.GET("/", func(c *gin.Context) {
			c.String(200, "后台首页")
		})
		adminRouters.GET("/user", func(c *gin.Context) {
			c.String(200, "用户列表")
		})
		adminRouters.GET("/article", func(c *gin.Context) {
			c.String(200, "新闻列表")
		})
	}
}

apiRoutes.go

// ApiRoutersInit 管理api路由
func ApiRoutersInit(r *gin.Engine) {
	apiRouters := r.Group("/api")
	{
		apiRouters.GET("/", func(c *gin.Context) {
			c.String(200, "api首页")
		})
		apiRouters.GET("/userlist", func(c *gin.Context) {
			c.String(200, "一个userlist接口")
		})
	}
}

defaultRoutes.go

// DefaultRoutesInit 管理默认路由
func DefaultRoutersInit(r *gin.Engine) {
	defaultRouters := r.Group("/")
	{
		defaultRouters.GET("/", func(c *gin.Context) {
			c.String(200, "首页")
		})
		defaultRouters.GET("/news", func(c *gin.Context) {
			c.String(200, "新闻")
		})
	}
}

配置 main.go

func main() {
	//初始化路由
	r := gin.Default()

	// 注册路由
	router.AdminRoutersInit(r)
	router.ApiRoutersInit(r)
	router.DefaultRoutersInit(r)

	// 运行服务器
	r.Run(":8080")
}

自定义控制器

在 Gin 框架中,控制器主要负责处理 HTTP 请求并返回响应,它是 MVC(Model-View-Controller)架构中的一部分。自定义控制器,顾名思义,就是由开发者根据业务需求自行创建和定义的控制器。

在 Gin 中,一个控制器通常是一个函数,它接收一个 *gin.Context 类型的参数,这个参数包含了当前 HTTP 请求的所有信息,如请求头、请求体、URL参数等。控制器可以通过这个参数来获取请求信息,进行业务逻辑处理,然后返回响应。

新建 controller/admin/ArticleController.go

package admin

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

type ArticleController struct{}

func (a ArticleController) GetArticle(c *gin.Context) {
	c.JSON(200, "获取文章")
}

func (a *ArticleController) AddArticle(c *gin.Context) {
	c.JSON(200, "添加文章")
}

func (a *ArticleController) EditArticle(c *gin.Context) {
	c.JSON(200, "编辑文章")
}

新建 controller/admin/IndexController.go

package admin

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

type IndexController struct {
}

func (con IndexController) Index(c *gin.Context) {
	c.String(200, "后台首页")
}

配置对应的路由

package router

import (
	"ginpro/gincontroller/controller/admin"
	"github.com/gin-gonic/gin"
)

// AdminRoutersInit  管理后台路由
func AdminRoutersInit(r *gin.Engine) {
	adminRouters := r.Group("/admin")
	{
		adminRouters.GET("/", admin.IndexController{}.Index)
		adminRouters.GET("/article", admin.ArticleController{}.GetArticle)
	}
}

控制器的继承

新建 controller/admin/BaseController

package admin

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

type BaseController struct{}

func (con BaseController) success(c *gin.Context) {
	c.String(http.StatusOK, "成功")
}

func (con BaseController) error(c *gin.Context) {
	c.String(http.StatusOK, "失败")
}

新建 controller/admin/UserController.goUserController 继承 BaseController,继承后就可以调用控制器里面的公共方法了。

package admin

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

type UserController struct {
	BaseController
}

func (con UserController) GetUserList(c *gin.Context) {
	//c.String(200, "用户列表1")
	con.success(c)
}

func (con UserController) AddUser(c *gin.Context) {
	c.String(200, "用户添加2")
}

func (con UserController) EditUser(c *gin.Context) {
	c.String(200, "用户编辑3")
}

Gin中间件

Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函 数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、 记录日志、耗时统计等。

通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作。

路由中间件

初始中间件

Gin 中的中间件必须是一个 gin.HandlerFunc 类型,配置路由的时候可以传递多个 func 回调函数,最后一个 func 回调函数前面触发的方法都可以称为中间件。

定义中间件:

func InitMiddleWareOne(c *gin.Context) {
	fmt.Println("hello world1")
	c.Next()
	fmt.Println("hello world2")
}

在路由中使用中间件:

r.GET("/hello2", InitMiddleWareOne, func(c *gin.Context) {
		c.JSON(200, gin.H{
			"msg": "hello world2",
		})
	})

ctx.Next()

ctx.Next() 是一个非常重要的方法,它在中间件中使用,用于调用后续的处理函数。

在 Gin 中,一个请求的处理流程是这样的:当一个请求到来时, Gin 会按照定义的顺序调用所有的中间件和处理函数。在每个中间件中,你可以选择在处理函数执行前后做一些操作,比如记录日志、处理错误、检查权限等。

这时候,ctx.Next() 就派上用场了。当你在一个中间件中调用 ctx.Next(),Gin 会继续执行后续的中间件和处理函数。如果你没有调用 ctx.Next(),那么后续的中间件和处理函数就不会被执行。

定义包含 ctx.Next() 的中间件

func TestCtxNext(c *gin.Context) {
	fmt.Println("1-执行中间件")
	start := time.Now().UnixNano()
	// 执行下一个中间件
	c.Next()
	fmt.Println("3-程序执行完成,计算时间")
	// 计算时间
	end := time.Now().UnixNano()
	fmt.Printf("middle time: %d\n", end-start)
}

在路由中使用中间件

r.GET("/", TestCtxNext, func(c *gin.Context) {
		fmt.Println("2-执行路由")
		time.Sleep(time.Second)
		c.String(200, "hello world")
	})

访问路由:http://localhost:8080/

控制台输出结果

1-执行中间件
2-执行路由
3-程序执行完成,计算时间
middle time: 1001261000

多个中间件执行顺序

接着我们重新定义两个中间件:

func InitMiddleWareOne(c *gin.Context) {
	fmt.Println("hello world1")
	c.Next()
	fmt.Println("hello world2")
}
func InitMiddleWareTwo(c *gin.Context) {
	fmt.Println("hello world3")
	c.Next()
	fmt.Println("hello world4")
}

在路由中使用中间件

r.GET("/hello2", InitMiddleWareOne, InitMiddleWareTwo, func(c *gin.Context) {
		c.JSON(200, gin.H{
			"msg": "hello world2",
		})
	})

访问路由:http://localhost:8080/hello2

控制台输出结果

hello world1
hello world3
hello world4
hello world2

ctx.Abort()

在 Gin 框架中,ctx.Abort() 是一个用于终止当前请求处理流程的方法。一旦调用了该方法,那么后续的中间件和处理函数都不会被执行。

ctx.Abort() 通常在中间件中使用,特别是在一些需要检查请求有效性或权限的中间件中。例如,如果有一个身份验证的中间件,在用户身份验证失败时调用 ctx.Abort() ,立即终止请求处理,返回一个错误响应。

编写中间件

func InitMiddleWare(c *gin.Context) {
	fmt.Println("hello world1")
	start := time.Now().UnixNano()

	c.String(200, "hello world")

	c.Abort()

	fmt.Println("hello world2")

	end := time.Now().UnixNano()

	fmt.Printf("middle time: %d\n", end-start)
}

在路由中使用中间件

r.GET("/hello", InitMiddleWare, func(c *gin.Context) {
		fmt.Println("hello world3")
		c.JSON(200, gin.H{
			"msg": "hello world",
		})
	})

访问路由:http://localhost:8080/hello

控制台输出

hello world1
hello world2
middle time: 52000

全局中间件

在 Gin 框架中,全局中间件是一种应用于所有路由的中间件。我们可以使用 router.Use() 方法来添加全局中间件。

// 全局中间件
// 使用每一个路由都会调用InitMiddleWareOne, InitMiddleWareTwo中间件
r.Use(InitMiddleWareOne, InitMiddleWareTwo)

在路由分组中使用中间件

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

// 写法一
// AdminRoutersInit  管理后台路由
func AdminRoutersInit(r *gin.Engine) {
	adminRouters := r.Group("/admin", middleware.InitMiddleWareOne, middleware.InitMiddleWareTwo)
	{
		...
	}
}

// 写法二
// AdminRoutersInit  管理后台路由
func AdminRoutersInit(r *gin.Engine) {
	adminRouters := r.Group("/admin")
 adminRouters.Use(middleware.InitMiddleWareOne)
	{
        adminRouters.GET("/index", func(c *gin.Context) {...}}
		...
	}
}

中间件和对应控制器之间共享数据

设置值

// MiddleWareSetValue 设置值
func MiddleWareSetValue(c *gin.Context) {
	// 可以通过 ctx.Set 在请求上下文中设置值,后续的处理函数能够取到该值
	c.Set("username", "ypb")
	c.String(200, "中间件设置值")
}

获取值

type Controller struct{}

// ControllerGetValue 获取值
func (c Controller) ControllerGetValue(ctx *gin.Context) {
	username, _ := ctx.Get("username")
	fmt.Println(username)
	ctx.String(200, "获取用户名成功")
}

路由请求 http://localhost:8080/getvalue

r.GET("/getvalue", MiddleWareSetValue, Controller{}.ControllerGetValue)

控制台输出

ypb

注意事项

Gin 中的默认中间件

gin.Default() 默认使用了 LoggerRecovery 中间件。

其中:

  • Logger 中间件将日志写入 gin.DefaultWriter,即使配置了 GIN_MODE=release
  • Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500 响应码。

如果不想使用上面两个默认的中间件,可以使用 gin.New() 新建一个没有任何默认中间件的 路由。

总结

本次,我们讲解了如何在 Gin 框架中使用静态文件,控制器,路由分组以及中间件相关等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值