您的当前位置:首页正文

gin框架基础

2024-11-29 来源:个人技术集锦

1.安装

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

2.使用get、post、put等http方法

func main() {
	// 使用默认中间件创建一个gin路由器
	// Default默认开启logger and recovery (crash-free) 中间件
    实例化一个gin的server对象
	router := gin.Default()
    //r := gin.New() New()就不会新建中间件
	
    //restful开发中使用
	router.GET("/someGet", getting)
	router.POST("/somePost", posting)
	router.PUT("/somePut", putting)
	router.DELETE("/someDelete", deleting)
	router.PATCH("/somePatch", patching)
	router.HEAD("/someHead", head)
	router.OPTIONS("/someOptions", options)

	// 默认启动的是 8080端口,也可以自己定义启动端口
	router.Run()
	// router.Run(":3000") for a hard coded port
}

补充

post创建数据,非幂等

put 更新数据,幂等

patch 局部更新数据,非幂等

此外,还有一个可以匹配所有请求方法的Any方法如下:

r.Any("/test", func(c *gin.Context) {...})

为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。

r.NoRoute(func(c *gin.Context) {
		c.HTML(http.StatusNotFound, "views/404.html", nil)
	})

3.路由分组

package main

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

func main() {
	router := gin.Default()
	// Simple group: v1
	v1 := router.Group("/v1")
	// 下面的大括号不依附于任何结构,单纯的看着舒服
	{
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint)
		v1.POST("/read", readEndpoint)
	}
	// Simple group: v2
	v2 := router.Group("/v2")
	{
		v2.POST("/login", loginEndpoint)
		v2.POST("/submit", submitEndpoint)
		v2.POST("/read", readEndpoint)
	}
	router.Run(":8082")
}

func readEndpoint(context *gin.Context) {
	
}

func submitEndpoint(context *gin.Context) {

}

func loginEndpoint(context *gin.Context) {

}

4.含参url

也称为path参数获取

package main

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

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
    //通过:来传递
    //通过c.Param来获取
	r.GET("/user/:name/:action/", func(c *gin.Context) {
		name := c.Param("name")
		action := c.Param("action")
        c.JSON(http.StatusOK,gin.H{
            "id":id,
            "action":action,
        })
	})
    //*会把/user/:name后包括/的所有字符都接收,慎用
    r.GET("/user/:name/*action", func(c *gin.Context) {
		name := c.Param("name")
		action := c.Param("action")
		c.String(http.StatusOK, "%s is %s", name, action)
	})


	r.Run(":8082") 
}

5.获取路由分组的参数

package main

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

type Person struct {
	ID   int    `uri:"id" binding:"required"`
	Name string `uri:"name" binding:"required"`
}

func main() {
	router := gin.Default()
	router.GET("/:name/:id", func(c *gin.Context) {
		var person Person
		if err := c.ShouldBindUri(&person); err != nil {
			c.Status(404)
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"name": person.Name,
			"id":   person.ID,
		})
	})

	router.Run(":8083")
}

6.获取参数

package main

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

func main() {
	router := gin.Default()
	router.GET("/welcome", welcome)
	router.POST("/form_post", formPost)
	router.POST("/post", getPost)
	router.Run(":8083")
}

//get请求
//data := c.DefaultQuery()会设置默认值
//data := c.Query()取不到返回空
//data,err := c.GetQuery()取不到会报错
func welcome(c *gin.Context) {
	//使用带默认值的获取参数
	firstName := c.DefaultQuery("firstname", "james")
	//不使用默认值的取值
	lastName := c.Query("lastname")
	c.JSON(http.StatusOK, gin.H{
		"first_name": firstName,
		"last_name":  lastName,
	})
}

/*
http://localhost:8083/welcome
{
"first_name": "james",
"last_name": ""
}
http://localhost:8083/welcome?firstname=tom&lastname=jerry
{
"first_name": "tom",
"last_name": "jerry"
}
*/

//post请求 
//c.DefaultPostForm()取不到值时会返回指定的默认值
//c.PostForm()取不到值时会返回空
func formPost(c *gin.Context) {
	message := c.PostForm("message")
	nick := c.DefaultPostForm("nick", "anonymous")
	c.JSON(http.StatusOK, gin.H{
		"message": message,
		"nick":    nick,
	})
}

/*
localhost:8083/form_post
{
    "message": "",
    "nick": "anonymous"
}

localhost:8083/form_post
message	nihao
nick	james
{
    "message": "nihao",
    "nick": "james"
}
*/

//get,post混合获取 通过post来获取
func getPost(c *gin.Context) {
	id := c.Query("id")
	page := c.DefaultQuery("page", "0")
	name := c.PostForm("name")
	message := c.DefaultPostForm("message", "mesg")
	c.JSON(http.StatusOK, gin.H{
		"id":      id,
		"page":    page,
		"name":    name,
		"message": message,
	})
}

/*
localhost:8083/post?id=1&page=2
message	nihao
name	james

{
    "id": "1",
    "message": "nihao",
    "name": "james",
    "page": "2"
}
*/

7.JSON、ProtoBuf 渲染(输出格式)

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"

	"GoStart/gin_start/ch06/proto"
)

func main() {
	router := gin.Default()
	router.GET("/moreJSON", moreJSON)
	router.GET("/someProtoBuf", returnProto)

	router.Run(":8083")
}

//返回json格式
func moreJSON(c *gin.Context) {
	var msg struct {
		Name    string `json:"user"`
		Message string
		Number  int
	}
	msg.Name = "james"
	msg.Message = "this is a test"
	msg.Number = 20

	c.JSON(http.StatusOK, msg)
}

/*
localhost:8083/moreJSON

{
    "user": "james",
    "Message": "this is a test",
    "Number": 20
}
*/

//返回ProtoBuf
func returnProto(c *gin.Context) {
	course := []string{"python", "go", "weifuwu"}
	user := &proto.Teacher{
		Name:   "james",
		Course: course,
	}
	c.ProtoBuf(http.StatusOK, user)
}

返回JSON

package main

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

func main() {

	r := gin.Default()

	r.GET("/mapjson", func(c *gin.Context) {
		//方法1:使用map
		data := map[string]interface{}{
			"name":    "james",
			"age":     7,
			"message": "mapjson",
		}

		c.JSON(http.StatusOK, data)
	})

	r.GET("/structjson", func(c *gin.Context) {
		//方法2:使用结构体
		//注意结构体字段首字母要大写,如果要小写可以通过tag来实现
		type msg struct {
			Name    string `json:"name"`
			Age     int    `json:"age"`
			Id      string
			Message string
		}
		data := msg{
			"james",
			18,
			"007",
			"structjson",
		}

		c.JSON(http.StatusOK, data)

		//返回值
		//{
		//	"name": "james",
		//	"age": 18,
		//	"Id": "007",
		//	"Message": "structjson"
		//}
	})

	r.GET("/ginHjson", func(c *gin.Context) {
		//方法3:gin.H约等于map的用法
		data := gin.H{
			"name":    "james",
			"age":     7,
			"message": "ginHjson",
		}

		c.JSON(http.StatusOK, data)
	})

	r.Run(":9090")
}

获取json

r.POST("/json", func(c *gin.Context) {
	// 注意:下面为了举例子方便,暂时忽略了错误处理
	b, _ := c.GetRawData()  // 从c.Request.Body读取请求数据
	// 定义map或结构体
	var m map[string]interface{}
	// 反序列化
	_ = json.Unmarshal(b, &m)

	c.JSON(http.StatusOK, m)
})

8.表单验证

ShouldBind会按照下面的顺序解析请求中的数据完成绑定:

package main

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

//登录表单验证
type LoginForm struct {
	//required表示必填字段,
	//binding:"required,min=3,max=10"`如果有多个绑定信息,用,隔开
	User     string `form:"user" json:"user" xml:"user"  binding:"required"`
	Password string `form:"password" json:"password" xml:"password" binding:"required"`
}

//注册表单验证
type SignUpForm struct {
	Age        uint8  `json:"age" binding:"gte=1,lte=130"`
	Name       string `json:"name" binding:"required,min=3"`
	Email      string `json:"email" binding:"required,email"`
	Password   string `json:"password" binding:"required"`
	RePassword string `json:"repassword" binding:"required,eqfield=Password"` //跨字段验证
}

func main() {
	router := gin.Default()
	router.POST("/loginJSON", func(c *gin.Context) {
		var loginForm LoginForm

		if err := c.ShouldBind(&loginForm); err != nil {
			fmt.Println(err.Error())
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"msg": "login success",
		})
	})
	/*
		{
		    "error": "Key: 'LoginForm.Password' Error:Field validation for 'Password' failed on the 'required' tag"
		}
	*/

	router.POST("/signup", func(c *gin.Context) {
		var signUpForm SignUpForm
		if err := c.ShouldBind(&signUpForm); err != nil {
			fmt.Println(err.Error())
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"msg": "sign up success",
		})
	})
	/*
			{
			    "age":18,
			    "name":"james",
			    "email":"1111@qq.com",
			    "password":"111",
			    "repassword":"111"
			}

		{
		    "msg": "sign up success"
		}
	*/

	router.Run(":8083")
}

9.返回值中文翻译器

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/en"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	enTranslations "github.com/go-playground/validator/v10/translations/en"
	zhTranslations "github.com/go-playground/validator/v10/translations/zh"
	"net/http"
)

var trans ut.Translator

//登录表单验证
type LoginForm struct {
	//required表示必填字段,
	//binding:"required,min=3,max=10"`如果有多个绑定信息,用,隔开
	User     string `form:"user" json:"user" xml:"user"  binding:"required"`
	Password string `form:"password" json:"password" xml:"password" binding:"required"`
}

//注册表单验证
type SignUpForm struct {
	Age        uint8  `json:"age" binding:"gte=1,lte=130"`
	Name       string `json:"name" binding:"required,min=3"`
	Email      string `json:"email" binding:"required,email"`
	Password   string `json:"password" binding:"required"`
	RePassword string `json:"repassword" binding:"required,eqfield=Password"` //跨字段验证
}

//修改中文翻译器
func InitTrans(local string) (err error) {
	//修改gin框架中的validator引擎属性,实现定制
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		zhT := zh.New() // 中文翻译器
		enT := en.New() // 英文翻译器
		// 第一个参数是备用参数,后面的参数是需要支持的语言环境
		uni := ut.New(enT, zhT)
		trans, ok = uni.GetTranslator(local)
		if !ok {
			return fmt.Errorf("uni.GetTranslator(%s)", local)
		}
		// 注册翻译器
		switch local {
		case "en":
			err = enTranslations.RegisterDefaultTranslations(v, trans)
		case "zh":
			err = zhTranslations.RegisterDefaultTranslations(v, trans)
		default:
			err = enTranslations.RegisterDefaultTranslations(v, trans)
		}
		return
	}
	return
}

func main() {
	if err := InitTrans("zh"); err != nil {
		fmt.Println("init translator error")
		return
	}

	router := gin.Default()
	router.POST("/loginJSON", func(c *gin.Context) {
		var loginForm LoginForm

		if err := c.ShouldBind(&loginForm); err != nil {
			errs, ok := err.(validator.ValidationErrors)
			if !ok {
				c.JSON(http.StatusOK, gin.H{
					"msg": err.Error(),
				})
			}

			c.JSON(http.StatusBadRequest, gin.H{
				"error": errs.Translate(trans),
			})
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"msg": "login success",
		})
	})
	/*
		{
		    "error": "Key: 'LoginForm.Password' Error:Field validation for 'Password' failed on the 'required' tag"
		}
	*/
	/*
			翻译为中文后
		{
		    "error": {
		        "LoginForm.User": "User为必填字段"
		    }
		}
	*/

	router.POST("/signup", func(c *gin.Context) {
		var signUpForm SignUpForm
		if err := c.ShouldBind(&signUpForm); err != nil {
			fmt.Println(err.Error())
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"msg": "sign up success",
		})
	})
	/*
			{
			    "age":18,
			    "name":"james",
			    "email":"1111@qq.com",
			    "password":"111",
			    "repassword":"111"
			}

		{
		    "msg": "sign up success"
		}
	*/

	router.Run(":8083")
}

10.gin中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。

注册中间件例1

package main

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

//自定义中间件
func MyLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()

		c.Set("example", "123456")
		//让原本该执行的逻辑继续执行
		c.Next()

		end := time.Since(t)
		fmt.Printf("use time:%V\n", end)

		status := c.Writer.Status()
		fmt.Println("status:", status)
	}
}

func main() {
    gin.Default默认使用Logger()和Recovery()中间件
    定义一个默认的无中间件的路由
	//router := gin.New()
	配置全局的中间件
	使用logger中间件,打印日志的作用
	//router.Use(gin.Logger())
	使用recovery中间件,出现panic的时候,会自动recover掉
	//router.Use(gin.Recovery())
	也可以写在一起
	router.Use(gin.Logger(),gin.Recovery())
	//
	只有Group的/goods可以使用这个中间件
	//authrized := router.Group("/goods")
	//authrized.Use(AuthRequired)

	router := gin.Default()
	router.Use(MyLogger())
	router.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "pong",
		})
	})
	router.Run(":8083")
}

func AuthRequired(context *gin.Context) {

}

注册中间件例2

package main

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

func indexHandler(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"msg": "index",
	})
	fmt.Println("index")
}

//中间件可以直接用参数为c *gin.Context的函数实现
func baseFunc(c *gin.Context) {
	fmt.Println("baseFunc in")
	name, _ := c.Get("name")
	fmt.Printf("get name:%s\n", name)
	c.Next()
	fmt.Println("baseFunc out")

}

//通过闭包实现中间件,实际开发中这样用
func countTime() gin.HandlerFunc {
	return func(c *gin.Context) {
		fmt.Println("countTime in")
		start := time.Now()
		c.Set("name", "james") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
		// 调用该请求的剩余处理程序
		c.Next()
		// 不调用该请求的剩余处理程序,直接返回,相当于函数中的return
		// c.Abort()
		// 计算耗时
		cost := time.Since(start)
		log.Printf("cost time:%d\n", cost)
		fmt.Println("countTime out")
	}
}

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

	//全局注册中间件
	r.Use(countTime())
	//r.GET("/index", indexHandler)

	//给单独的router注册中间件
	r.GET("/index", baseFunc, countTime(), indexHandler)

	r.Run(":9090")
}

为路由组注册中间件

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

写法1:

shopGroup := r.Group("/shop", countTime())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

写法2:

shopGroup := r.Group("/shop")
shopGroup.Use(countTime())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

go中间件的实现原理

c.Next()会调用一个index++

中间件会全部加入一个队列,前面的中间件函数执行结束后,返回自己,而队列后面的中间件还会继续执行

所以如果想要中间件函数执行就结束,需要使用c.Abort(),这个函数会将index指向最后,使整个流程终止.

c.Abort()

中间件中的return无法阻止后续逻辑的执行,而c.Abort()可以

11.优雅的退出

package main

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

func main() {
	//优雅退出
	//微服务,在启动前后会将当前服务的ip地址和端口号注册到注册中心
	//当前服务停止后,并没有告诉注册中心下线服务,
	router := gin.Default()

	router.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "pong",
		})
	})

	go func() {
		router.Run(":8083")

	}()

	//接收到关闭信号  kill -9 强杀命令
	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	//处理后续逻辑
	fmt.Println("closing server")
	fmt.Println("logout server")

}

12.go的微服务框架

go-micro

kratos

rpcx

13.重定向

HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。

r.GET("/test", func(c *gin.Context) {
	c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/")
})

路由重定向

路由重定向,使用HandleContext

r.GET("/test", func(c *gin.Context) {
    // 指定重定向的URL
    c.Request.URL.Path = "/test2"
    r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"hello": "world"})
})
显示全文