Gin框架的安装和基本使用

Gin框架的安装和使用


文章目录

  • Gin框架的安装和使用
    • 安装Gin框架
    • RESTful API
    • Gin渲染
      • JSON渲染
      • protobuf渲染
    • 获取参数
      • 获取querystring参数
      • 获取form参数
      • 获取json参数
      • 获取path参数
      • 参数绑定
    • 文件上传
      • 单个文件上传
      • 多个文件上传
    • 重定向
      • HTTP重定向
      • 路由重定向
    • Gin路由
      • 普通路由
      • 路由组
      • 路由原理
    • 运行多个服务


安装Gin框架

go get github.com/gin-gonic/gin

示例

package main

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

type UserInfo struct {
	Name string
	Gender string
	Age int
}

func main() {
	r := gin.Default()
	r.GET("/hello", func(context *gin.Context) {
		context.JSON(http.StatusOK, &UserInfo{
			Name: "light",
			Gender: "male",
			Age: 21,
		})
	})
	r.Run()
}

将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello就能看到一串JSON字符串。

RESTful API

REST是一种软件架构风格,简单来说,就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源。

例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,按照RESTful API设计如下:

请求方法 URL 含义
GET /book 查询数据信息
POST /book 创建书籍信息
PUT /book 更新书籍信息
DELETE /book 删除书籍信息

GIn框架支持RESTful API的开发

func main() {
	r := gin.Default()
	r.GET("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "GET",
		})
	})

	r.POST("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "POST",
		})
	})

	r.PUT("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "PUT",
		})
	})

	r.DELETE("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "DELETE",
		})
	})
}

Gin渲染

这里说的渲染主要是以某种数据格式比如json向客户端发送数据,一般写在回调函数中。然后客户端浏览器会将这些数据渲染在HTML页面中。

JSON渲染

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

	// gin.H 是map[string]interface{}的缩写
	r.GET("/someJSON", func(c *gin.Context) {
		// 方式一:自己拼接JSON
		c.JSON(http.StatusOK, gin.H{"message": "Hello world!"})
	})
	r.GET("/moreJSON", func(c *gin.Context) {
		// 方法二:使用结构体
		var msg struct {
			Name    string `json:"user"`
			Message string
			Age     int
		}
		msg.Name = "张三"
		msg.Message = "Hello world!"
		msg.Age = 21
		c.JSON(http.StatusOK, msg)
	})
	r.Run(":8080")
}

protobuf渲染

user.proto:

syntax = "proto3";

option go_package = "./;protoFile";
package protoFile;

message User{
    string name = 1;
    string gender = 2;
    int32 age = 3;
}

r.GET("/proto", func(c *gin.Context) {
		data := &protoFile.User{
			Name:   "dawnlight",
			Gender: "男",
			Age:    20,
		}
		c.ProtoBuf(http.StatusOK, data)
	})

	r.Run(":9090")

获取参数

获取querystring参数

querystring指的是URL中?后面携带的参数,例如:/user/search?username=张三&address=上海。 获取请求的querystring参数的方法如下:

func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.GET("/user/search", func(c *gin.Context) {
		username := c.DefaultQuery("username", "张三")
		//username := c.Query("username")
		address := c.Query("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})
	r.Run()
}

获取form参数

当前端请求的数据通过form表单提交时,例如向/user/search发送一个POST请求,获取请求数据的方式如下所示:

func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.POST("/user/search", func(c *gin.Context) {
		// DefaultPostForm取不到值时会返回指定的默认值
		//username := c.DefaultPostForm("username", "张三")
		username := c.PostForm("username")
		address := c.PostForm("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})
	r.Run(":8080")
}

获取json参数

当前端请求的数据通过JSON提交时,例如向/json发送一个POST请求,则获取请求参数的方式如下:

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)
})

更便利的获取请求参数的方式,参见下面的 参数绑定 小节。

获取path参数

func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.GET("/user/search/:username/:address", func(c *gin.Context) {
		username := c.Param("username")
		address := c.Param("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})

	r.Run(":8080")
}

参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString、form表单、JSON、XML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象。

// Binding from JSON
type Login struct {
	User     string `form:"user" json:"user" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

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

	// 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})
	router.POST("/loginJSON", func(c *gin.Context) {
		var login Login

		if err := c.ShouldBind(&login); err == nil {
			fmt.Printf("login info:%#v\n", login)
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 绑定form表单示例 (user=q1mi&password=123456)
	router.POST("/loginForm", func(c *gin.Context) {
		var login Login
		// ShouldBind()会根据请求的Content-Type自行选择绑定器
		if err := c.ShouldBind(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)
	router.GET("/loginForm", func(c *gin.Context) {
		var login Login
		// ShouldBind()会根据请求的Content-Type自行选择绑定器
		if err := c.ShouldBind(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// Listen and serve on 0.0.0.0:8080
	router.Run(":8080")
}

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

  • 如果是 GET 请求,只使用 Form 绑定引擎(query)。
  • 如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)。

示例:

html表单:

<form method="post" action="/user">
        <tr>
            <label>用户名:label>
            <input type="text" name="username"> <br>
        tr> 
        <tr>
            <label>密码:label>
            <input type="password" name="password" > <br>
        tr>
        <tr>
            <input type="submit" value="提交">
        tr>
    form>

利用反射来提取表单数据,与结构体绑定在一起:

type UserInfo struct{
	Name string `form:"username"`
	Pwd string `form:"password"`
}

func main() {
	r := gin.Default()
	r.LoadHTMLFiles("2formTest\\userInfo.html")
	r.GET("/user", func(c *gin.Context) {
		c.HTML(http.StatusOK, "userInfo.html", nil)
	})
	r.POST("/user", func(c *gin.Context) {
		var user UserInfo
		if err := c.ShouldBind(&user); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"username" : user.Name,
				"password" : user.Pwd,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})
	r.Run(":9090")
}

文件上传

单个文件上传

文件上传前端页面代码:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>上传文件title>
head>
<body>
    <form action="/upload" method="post" enctype="multipart/form-data">
        
        
        
        <input type="file" name="file" multiple>
        <input type="submit" value="上传">
    form>
body>
html>

后端gin框架部分代码:

func main() {
	r := gin.Default()
	r.LoadHTMLFiles("3uploadFile/upload.html")
	r.GET("/upload", func(c *gin.Context) {
		c.HTML(http.StatusOK, "upload.html", nil)
	})
	r.POST("/upload", func(c *gin.Context) {
		file, err := c.FormFile("f1")
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"message" : err.Error(),
			})
			return
		}
		log.Println(file.Filename)
		dst := fmt.Sprintf("F:\\GoProject\\Gin\\ginDemo1\\UploadFile\\%s", file.Filename)
		c.SaveUploadedFile(file, dst)
		c.JSON(http.StatusOK, gin.H{
			"message" : fmt.Sprintf("'%s' uploaded!", file.Filename),
		})
	})
	r.Run(":9090")
}

多个文件上传

func main() {
	r := gin.Default()
	r.LoadHTMLFiles("3uploadFile/upload.html")
	r.GET("/upload", func(c *gin.Context) {
		c.HTML(http.StatusOK, "upload.html", nil)
	})
	r.POST("/upload", func(c *gin.Context) {
		form, _ := c.MultipartForm()
		files := form.File["file"]

		for index, file := range files {
			log.Println(file.Filename)
			dst := fmt.Sprintf("F:\\GoProject\\Gin\\ginDemo1\\UploadFile\\%s_%d", file.Filename, index)
			c.SaveUploadedFile(file, dst)
		}
		c.JSON(http.StatusOK, gin.H{
			"message" : fmt.Sprintf("%d files uploaded!", len(files)),
		})
	})
	r.Run(":9090")
}

重定向

HTTP重定向

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

func main() {
	r := gin.Default()
	r.GET("/red", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
	})
	r.Run(":9092")
}

路由重定向

路由重定向,使用HandleContext:

r.GET("/test", func(c *gin.Context) {
		c.Request.URL.Path = "/test2"
		r.HandleContext(c)
	})
	r.GET("/test2", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message" : "route redirect successful",
		})
	})

Gin路由

普通路由

r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})

此外,还有一个可以匹配所有请求方法的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)
	})

路由组

我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。

func main() {
	r := gin.Default()
	userGroup := r.Group("/user")
	{
		userGroup.GET("/index", func(c *gin.Context) {...})
		userGroup.GET("/login", func(c *gin.Context) {...})
		userGroup.POST("/login", func(c *gin.Context) {...})

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

路由组也是支持嵌套的,例如:

shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/index", func(c *gin.Context) {...})
		shopGroup.GET("/cart", func(c *gin.Context) {...})
		shopGroup.POST("/checkout", func(c *gin.Context) {...})
		// 嵌套路由组
		xx := shopGroup.Group("xx")
		xx.GET("/oo", func(c *gin.Context) {...})
	}

通常我们将路由分组用在划分业务逻辑或划分API版本时。

路由原理

Gin框架中的路由使用的是httprouter这个库。

其基本原理就是构造一个路由地址的前缀树。

运行多个服务

我们可以在多个端口启动服务,例如:

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/sync/errgroup"
)

var (
	g errgroup.Group
)

func router01() http.Handler {
	e := gin.New()
	e.Use(gin.Recovery())
	e.GET("/", func(c *gin.Context) {
		c.JSON(
			http.StatusOK,
			gin.H{
				"code":  http.StatusOK,
				"error": "Welcome server 01",
			},
		)
	})

	return e
}

func router02() http.Handler {
	e := gin.New()
	e.Use(gin.Recovery())
	e.GET("/", func(c *gin.Context) {
		c.JSON(
			http.StatusOK,
			gin.H{
				"code":  http.StatusOK,
				"error": "Welcome server 02",
			},
		)
	})

	return e
}

func main() {
	server01 := &http.Server{
		Addr:         ":8080",
		Handler:      router01(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	server02 := &http.Server{
		Addr:         ":8081",
		Handler:      router02(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
   // 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
	g.Go(func() error {
		return server01.ListenAndServe()
	})

	g.Go(func() error {
		return server02.ListenAndServe()
	})

	if err := g.Wait(); err != nil {
		log.Fatal(err)
	}
}

参考资料:

https://www.liwenzhou.com/posts/Go/Gin_framework/

你可能感兴趣的:(Gin,golang)