gin阶段学习(入门)

1.环境配置

假设你的go环境已经配置好了

  • 打开终端进行配置
mkdir -p $GOPATH/src/github.com/zxhjames/Zonst 
//在gopath的目录下新建该文件夹(在这里我以自己的github仓库地址命名为主)
cd $_
//进入该项目根目录 
go get -v github.com/gin-gonic/gin
//获取gin框架

2.测试运行

  • 进入该项目,新建一个子项目
pwd //显示在当前的项目根目录下 GOPATH/src/github.com/zxhjames/Zonst
mkdir -p gindemo1/main && cd gindemo1/main
vi main.go
  • 编写测试文件
package main
import "github.com/gin-gonic/gin"
func main(){
	//1.创建gin的实例
	router:=gin.Default()
	//2.发送get请求,并声明回调函数
	router.GET("/ping",func(c *gin.Context){
	//3.返回一个json格式的数据
		c.JSON(200,gin.H{
			"message":"pong",
		})
	})
	//4.启动服务器,默认监听8080端口
	router.Run()
  • 查看返回数据
{
	"message": "pong"
}

3.请求路由

请求路由分为

  • 多种请求类型 (get/post/delete/put…等8种请求类型)
  • 绑定静态文件夹 (可以将gin作为静态服务器使用)
  • 参数作为url (多见于restful服务中)
  • 泛绑定 (类似于nginx的路由匹配)

3.1 多种请求类型

package main

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

func main(){
	r:=gin.Default()
	
	//get
	r.GET("/get", func(context *gin.Context) {
		context.String(200,"get")
	})

	//post
	r.GET("/post", func(context *gin.Context) {
		context.String(200,"post")
	})
	
	//delete
	r.Handle("DELETE","/delete", func(context *gin.Context) {
		context.String(200,"delete")
	})
	
	//设置多个路由方法,多种请求都可以同时打到any上
	r.Any("any", func(context *gin.Context) {
		context.String(200,"any")
	})
	r.Run()
}

3.2 静态文件夹

这种方式可以直接将本地的静态资源映射到服务器的URL上

  • 新建项目文件夹,在main文件夹下新建一个assets和static文件夹,分别在两个文件夹下新建html文件,如图:
    在这里插入图片描述

  • 编写main.go文件

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)
/**
请求路由,静态文件夹
 */
func main(){
	r:=gin.Default()
	//设置静态文件夹绑定
	r.Static("/assets","./assets")
	//或者另外一种方法
	r.StaticFS("/static",http.Dir("static"))
	//设置单个静态资源
	r.StaticFile("/favicon.ico","./favicon.ico")
	r.Run()
}
  • 在终端运行
go build -o router_static && ./router_static

注意这里不能直接在goland里运行,会访问不到的,最后使用

curl "http://localhost:8080/assets/a.html"
curl "http://localhost:8080/static/b.html"

可以分别访问到静态资源

3.3 参数作为URL

  • 新建文件夹,main.go写入
package main

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

func main(){
	r:=gin.Default()
	r.GET("/:name/:id", func(context *gin.Context) {
		context.JSON(200,gin.H{
			"name":context.Param("name"),
			"id":context.Param("id"),
		})
	})
	r.Run()
}

  • 测试结果
    访问 http://localhost:8080/james/10
    输出
{
   "id": "10",
   "name": "james"
}

4.获取请求参数

  • 获取get请求
  • 获取post请求
  • 获取body值
  • 获取参数绑定结构体(重要!)

4.1 获取get请求

package main

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

func main(){
	//声明gin实例
	r:=gin.Default()
	r.GET("/test", func(context *gin.Context) {
		//获取第一个参数
		firstName := context.Query("first_name")
		//获取第二个参数,有默认值
		lastName := context.DefaultQuery("last_name",
			"last_default_name")
		context.String(200,"%s,%s",firstName,lastName)
	})
	r.Run()
}

可以看到,第二个参数传不传值是有区别的

curl -X GET 'http://localhost:8080/test?first_name=james&last_name=vincent' //output james,vincent

curl -X GET 'http://localhost:8080/test?first_name=james' //output james

4.2 泛绑定

package main

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

func main(){
	r:=gin.Default()
	// 所有以/user/为前缀的的URL都要打到helloworld上
	r.GET("/user/*action", func(context *gin.Context) {
		context.String(200,"hello world")
	})
	r.Run()
}
curl -X GET 'http://localhost:8080/user/hhh
curl -X GET 'http://localhost:8080/user/xxx

由于泛绑定了以/user为前缀的地址,可以看到最终都会显示hello world

4.3 获取body内容值

package main

import (
	"bytes"
	"github.com/gin-gonic/gin"
	"io/ioutil"
	"net/http"
)
/**
获取body内容
 */
func main(){
	r:=gin.Default()
	r.POST("/test", func(context *gin.Context) {
		//拿到body的字节内容
		bodyByts,err:=ioutil.ReadAll(context.Request.Body)
		if err != nil {
			//如果抛异常则打印错误
			context.String(http.StatusBadRequest,err.Error())
			context.Abort()
		}else{
			/**
			正确则输出正确内容
			 */
			context.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyByts))
			firstName:=context.PostForm("firstName")
			lastName:=context.DefaultPostForm("lastName","default")
			context.String(200,"%s,%s,%s",firstName,lastName,string(bodyByts))

		}
	})
	r.Run()
}

下面是apipost返回的结果
在这里插入图片描述

4.4 获取bind参数

package main

import (
	"github.com/gin-gonic/gin"
	"time"
)
/**
测试传递结构体
 */
type Person struct {
	/**
	结构体参数绑定表单参数
	 */
	Name string `form:"name"`
	Address string `form:"address"`
	Birthday time.Time `form:"birthday" time_format:"2006-01-04"` //设置日期格式
}
func main(){
	r:=gin.Default()
	r.GET("/testing",testing)
	r.POST("/testing",testing)
	r.Run()
}

func testing(context *gin.Context){
	var person Person
	/**
	这里是根据请求的content-type来做不同的bind操作
	 */
	//绑定结构体
	if err:=context.ShouldBind(&person);err== nil {
		//如果请求正确
		context.String(200,"%v",person)
	}else{
		//如果请求参数错误
		context.String(200,"person bind error:%v",err)
	}
}

测试post请求
在这里插入图片描述
测试get请求
在这里插入图片描述

5.验证请求参数

  • 结构体验证
  • 自定义验证(结构体验证不满足时)
  • 升级验证-支持多语言错误信息

5.1 结构体验证

package main

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

/**
验证结构体参数的验证
 */
type Person struct {
	/**
	结构体参数绑定表单参数
	*/

	//bind中有连续条件都要满足时,要使用,间隔,如果要求任意一个能满足那么就会用| 隔开
	Age int `form:"age" binding:"required,gt=10"` 	//required表示三个参数必传,其中的age还要大于10adna
	Address string `form:"address" binding:"required"`
	Name string `form:"name" binding:"required"`
}

func main(){
	r:=gin.Default()
	r.GET("/testing", func(context *gin.Context) {
		var person Person
		//声明验证参数
		if err:=context.ShouldBind(&person);err!=nil {
			context.String(500,"%v",err)
		}
		context.String(200,"%v",person)
	})
	r.Run()
}

如果我们传入的age值小于或等于10,那么就不能满足验证条件,会访问失败
在这里插入图片描述

5.2 自定义验证器

package main

import (
	"net/http"
	"reflect"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"gopkg.in/go-playground/validator.v8"
)

// Booking contains binded and validated data.
type Booking struct {
	//绑定验证器 bookabledate ,同时设置日期格式
	CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
	//要保证checkout时间要大于checkin时间
	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

func bookableDate(
	v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
	field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
	/**
	重写验证器,预定时间一定要保证大于今天的日期
	 */
	if date, ok := field.Interface().(time.Time); ok {
		today := time.Now()
		if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
			return false
		}
	}
	return true
}

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

	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		v.RegisterValidation("bookabledate", bookableDate)
	}

	route.GET("/bookable", getBookable)
	route.Run(":8080")
}

func getBookable(c *gin.Context) {
	var b Booking
	if err := c.ShouldBindWith(&b, binding.Query); err == nil {
		c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
	} else {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	}
}

下面进行测试,首先我们输入一个合法的表单,两个日期都大于今天的日期,显然是合法的
在这里插入图片描述
下面是不合法的例子,这里的check_in还是昨天的日期,显然是不合法的
在这里插入图片描述

6.中间件

  • 使用gin的中间件
  • 自定义ip白名单中间件

6.1 使用gin的中间件

package main

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

//gin的中间件
func main(){
	
	//将日志结果输出到文件
	f,_:=os.Create("gin.log")
	gin.DefaultWriter = io.MultiWriter(f) // 将普通日志写入文件
	gin.DefaultErrorWriter = io.MultiWriter(f) // 将错误日志写入文件

	r:=gin.New() // New()也是中间件

	r.Use(gin.Logger(),gin.Recovery()) //使用recovery捕获错误,防止进程挂掉
	r.GET("/test", func(context *gin.Context) {
		name:=context.DefaultQuery("name","defaultName")
		context.String(200,"%s",name)
	})
	r.Run()
}

接下来进行测试,每次测试完,我们都可以在项目的根目录下找到该日志文件
在这里插入图片描述

6.2 使用自定义的中间件

这次我们定义一个过滤IP地址的中间件,判断请求的IP数组中是否有与主机相同放入IP

package main

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

/**
2.17 自定义白名单中间件
 */
func IPAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		/**
		设置白名单服务器列表
		 */
		ipList:=[]string{
			"127.0.0.1",
			//"127.0.0.2",
		}
		flag:=false
		clientIP:=c.ClientIP()
		for _,host:=range ipList{
			if clientIP == host {
				flag = true
				break
			}
		}
		if !flag {
			c.String(401,"%s not in iplist",clientIP)
			c.Abort()
		}
	}
}
func main(){
	r:=gin.Default()
	/**
	使用自己定义的文件
	 */
	r.Use(IPAuthMiddleware())
	r.GET("/test", func(context *gin.Context) {
		context.String(200,"hello test")
	})
	r.Run()
}

本机的ip为127.0.0.1,所以启动后,会显示 “hello test”,然而如果将请求数组中的IP改为127.0.0.2,那么将显示不再iplist中

7.gin的其他补充

  • 优雅关停
  • 模版渲染
  • 自动证书配置

7.1 优雅关停服务器

传统方法(上)和本例方法的区别
在这里插入图片描述

package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

/**
优雅关停
 */

func main(){
	r:=gin.Default()
	r.GET("/test", func(context *gin.Context) {
		//休眠5五秒
		time.Sleep(5*time.Second)
		context.String(200,"hello world")
	})
	/**
	创建一个server对象
	 */
	srv:=&http.Server{
		Addr:              ":8085",
		Handler:           r,
		TLSConfig:         nil,
		ReadTimeout:       0,
		ReadHeaderTimeout: 0,
		WriteTimeout:      0,
		IdleTimeout:       0,
		MaxHeaderBytes:    0,
		TLSNextProto:      nil,
		ConnState:         nil,
		ErrorLog:          nil,
	}
	/**
	将server放入协程
	 */
	go func() {
		/**
		如果报错,或者是服务器没有开启,就报错
		 */
		if err:=srv.ListenAndServe();err!=nil && err!=http.ErrServerClosed{
			log.Fatalf("listen:%s\n",err)
		}
	}()

	//定义一个信号
	quit:=make(chan os.Signal)
	//阻塞channal
	signal.Notify(quit,syscall.SIGINT,syscall.SIGTERM)
	<-quit
	log.Println("shutdown Server...")
	//设置超时显示
	ctx,cancel:=context.WithTimeout(context.Background(),10*time.Second)
	defer cancel()
	//关服务器
	if err:=srv.Shutdown(ctx);err!=nil{
		log.Fatal("server shutdown:",err)
	}
	log.Println("server exiting")
}

运行程序,由于我们给请求的响应时间设定为5秒,所以当我们另外开一个终端
输入curl -X GET "http://127.0.0.1:8085/test",并且马上关闭服务器,这时可以发现,终端仍然可以显示出hello world,同时服务器终端也显示了server shutdown

7.2 模版渲染

这次学习一下模版渲染的技术,首先我们在项目的main文件夹下面新建一个template文件夹,在里面新建一个a.html文件,并且定义一个变量title

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{{.title}}
</body>
</html>

接下来去创建main.go文件,如下

package main
import "github.com/gin-gonic/gin"
func main(){
	r:=gin.Default()
	r.LoadHTMLGlob("template/*")
	r.GET("/index", func(context *gin.Context) {
		context.HTML(200,"a.html",gin.H{
			//绑定到html的属性上
			"title":"a.html",
		})
	})
	/**
	注意,这一步要在手动启动服务器,不能直接在golang里面run
	 */
	r.Run()
}

接下来去访问http://localhost:8080/index就可以看到我们的静态页面和里面的属性值了
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值