Go语言Gin-1.路由

1.gin简介

  • 镜像配置:

    // 安装出现timeout问题可以配置go module镜像,终端执行下面命令,为阿里镜像
    go env -w GO111MODULE=on
    go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
    
  • 安装gin:

    import "github.com/gin-gonic/gin"
    
  • 一切始于hello world

    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")
    }
    
  • 当运行时候会出现如下错误;

    can't load package: package gin_demo is not in GOROOT
    // 可以使用go mod模式,在当前项目目录下初始化mod
    go mod init
    
    
  • 此时访问127.0.0.1:8000就会返回hello world。

2.gin路由

2.1 基本路由

  • gin框架中采用的路由哭给予httpprouter做的。

    packge main
    import (
        "net/http"
        "github.com/gin-gonic/gin"
    )
    
    func main() {
        r := gin.Default()
        r.GET("/", func(c *gin.Context) {
            c.String(http.StatusOK, "hello word")
        })
        r.POST("/xxxpost",getting)
        r.PUT("/xxxput")
        //监听端口默认为8080
        r.Run(":8000")
    }
    

2.2 Restful风格API

  • Gin 支持Restful风格API。表现层状态转化,如

    1.获取文章 /blog/getXxx Get blog/Xxx
    
    2.添加 /blog/addXxx POST blog/Xxx
    
    3.修改 /blog/updateXxx PUT blog/Xxx
    
    4.删除 /blog/delXxxx DELETE blog/Xxx
    

2.3API参数

  • API参数,可以通过Context 的Param方法来获取API参数。

    func main() {
    	r := gin.Default()
    	r.GET("/user/:name/*action", func(context *gin.Context) {
    		// 获取URL上name
    		name := context.Param("name")
    		// 获取URL上action
    		action := context.Param("action")
    		fmt.Println(action)
    		// 去除两边 /
    		action = strings.Trim(action, "/")
    		context.String(http.StatusOK, name+" is "+action)
    	})
    	r.Run(":8000")
    }
    
    
  • 如下图:

    ![F3A7649C-316C-49E1-84C2-0A5909EC46E7](/Users/xujunkai/Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ/Users/874524334/QQ/Temp.db/F3A7649C-316C-49E1-84C2-0A5909EC46E7.png)

2.4URL参数

  • URL参数可以通过DefaultQuery()或Query()方法获取。

  • DefaultQuery()若参数不对称,则返回默认值。Query()若不存在,返回空串。

    r.GET("userinfo", func(c *gin.Context){
    		name := c.DefaultQuery("name","Tom")
    		c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
    	})
    
  • 当不加参数访问URL:http://127.0.0.1:8000/userinfo,返回是:hello Tom,当加参数访问URL:http://127.0.0.1:8000/userinfo/?name=Jery,返回是:hello Jery

2.5表单参数

  • 表单传输为post请求,http常见的传输格式为:

    • application/json
    • application/x-www-form-urlencoded
    • application/xml
    • multipart/form-data
  • 表单可以通过如下方式访问,该方法默认解析x-www-form-urlencoded或from-data格式的参数。

    <form action="http://localhost:8000/form" method="post" action="application/x-www-form-urlencoded">
        用户名:<input type="text" name="username" placeholder="请输入你的用户名">  <br>
        密&nbsp;&nbsp;&nbsp;码:<input type="password" name="userpassword" placeholder="请输入你的密码">  <br>
        <input type="submit" value="提交">
    </form>
    
  • post请求处理:

    r.POST("/form", func(c *gin.Context) {
    		types := c.DefaultPostForm("type", "post")
    		username := c.PostForm("username")
    		password := c.PostForm("userpassword")
    		c.String(http.StatusOK,fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types))
    	})
    
  • 如下示例图:

    image-20200719145622507

    ![73478227-4D2D-4890-A7A8-B25BA628A84E](/Users/xujunkai/Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ/Users/874524334/QQ/Temp.db/73478227-4D2D-4890-A7A8-B25BA628A84E.png)

2.6上传单个文件

  • gin文件上传与原生的net/http方法类似,不同在于gin把原声request封装到c.Request中

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
              上传文件:<input type="file" name="file" >
              <input type="submit" value="提交">
        </form>
    </body>
    </html>
    
    package main
    
    import (
        "github.com/gin-gonic/gin"
    )
    
    func main() {
        r := gin.Default()
        //限制上传最大尺寸
        r.MaxMultipartMemory = 8 << 20
        r.POST("/upload", func(c *gin.Context) {
            file, err := c.FormFile("file")
            if err != nil {
                c.String(500, "上传图片出错")
            }
            // c.JSON(200, gin.H{"message": file.Header.Context})
            c.SaveUploadedFile(file, file.Filename)
            c.String(http.StatusOK, file.Filename)
        })
        r.Run()
    }
    
  • 上传特定文件

    // 获取headers
    _, headers, err := c.Request.FormFile("file")
    if err != nil {
      log.Printf("error")
    }
    if headers.Size > 1024*1024*2 {
      fmt.Println("文件太大了")
      return
    }
    if headers.Header.Get("Content-Type") != "image/png" {
      fmt.Println("只允许上传png图片")
      return
    }
    
    

2.7上传多个文件

func main() {
   // 1.创建路由
   // 默认使用了2个中间件Logger(), Recovery()
   r := gin.Default()
   // 限制表单上传大小 8MB,默认为32MB
   r.MaxMultipartMemory = 8 << 20
   r.POST("/upload", func(c *gin.Context) {
      form, err := c.MultipartForm()
      if err != nil {
         c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
      }
      // 获取所有图片
      files := form.File["files"]
      // 遍历所有图片
      for _, file := range files {
         // 逐个存
         if err := c.SaveUploadedFile(file, file.Filename); err != nil {
            c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
            return
         }
      }
      c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
   })
   //默认端口号是8080
   r.Run(":8000")
}

2.8路由的routes group

  • 为了管理一些相同的URL
func main() {
   // 1.创建路由
   // 默认使用了2个中间件Logger(), Recovery()
   r := gin.Default()
   // 路由组1 ,处理GET请求
   v1 := r.Group("/v1")
   // {} 是书写规范
   {
      v1.GET("/login", login)
      v1.GET("submit", submit)
   }
   v2 := r.Group("/v2")
   {
      v2.POST("/login", login)
      v2.POST("/submit", submit)
   }
   r.Run(":8000")
}

2.9路由原理

  • httproter会将所有路由规则构造一颗前缀树。比如有root and as at cn com

2.10路由拆分与注册

2.10.1 基本路由注册

package main

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

func helloHandler(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "Hello www.topgoer.com!",
	})
}
func main()  {
	r := gin.Default()
	// 指定路由函数。
	r.GET("/topgoer", helloHandler)
	if err := r.Run(); err!=nil{
		fmt.Println("startup service failed. err:%v
", err)
	}
}
// 访问http://localhost:8080/topgoer 返回{"message":"Hello www.topgoer.com!"}

2.10.2 路由拆分成单独文件或包

  • 当你的项目规模太大,就不适合在项目的main.go文件中去实现路由注册相关逻辑了。我们会倾向于把路由部分的代码拆分出来。形成单独一个文件或包

    // 项目目录结构
    gin_demo
    	|__go.sum
    	|__main.go
    	|__go.mod
    	|__routers
    			 |___routers.go
    	
    
  • Routers/routers.go

    package routers
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    func helloHandler(c *gin.Context){
    	c.JSON(http.StatusOK, gin.H{
    		"message": "Hello www.topgoer.com",
    	})
    }
    // SetupRouter 用于配置路由信息
    func SetupRouter() *gin.Engine {
    	r := gin.Default()
    	r.GET("/topgoer", helloHandler)
    	return r
    }
    
    
  • main.go

    package main
    
    import (
    	"fmt"
    	"gin_demo/routers"
    )
    
    func main() {
    	r :=routers.SetupRouter()
    	if err:= r.Run(); err != nil {
    		fmt.Printf("startUp service failed, err:%v
    ", err)
    	}
    }
    

2.10.3 路由拆分成多个文件

  • 当我们业务规模继续膨胀时候,单独的一个routers文件包已经无法满足,我们需求如下的目录

    // 项目目录结构
    gin_demo
    	|__go.sum
    	|__main.go
    	|__go.mod
    	|__routers
    			 |___blog.go
    			 |___shop.go
    

2.10.4路由拆分到不同APP

  • 有时候项目实在太大,那么我们倾向于把业务拆分更详细一些,例如把不同业务代码拆分成不同APP

    gin_demo
      |__app
    	|   |__blog
    	|		|   |__handler.go
      |   |   |__router.go
    	|   |__shop
    	|       |__handler.go
    	|				|__router.go
    	|__go.sum
    	|__main.go
    	|__go.mod
    	|__routers
    			 |___routers.go
    
  • app/blog/handler.go

package blog

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

func blogHandler(c *gin.Context){
	c.JSON(http.StatusOK, gin.H{
		"message": "Hello blog handler",
	})
}

  • app/blog/router.go

    package blog
    
    import "github.com/gin-gonic/gin"
    
    func Routers(e *gin.Engine){
    	e.GET("/blog",blogHandler)
    }
    
  • app/shop/handler.go

    package shop
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    func helloHandler(c *gin.Context){
    	c.JSON(http.StatusOK, gin.H{
    		"message": "Hello handler",
    	})
    }
    
    func commentHandler(c *gin.Context) {
    	c.JSON(http.StatusOK, gin.H{
    		"message": "comment handler",
    	})
    }
    
    
  • app/shop/router.go

    package shop
    
    import "github.com/gin-gonic/gin"
    
    func Routers(e *gin.Engine) {
    	e.GET("/hello",helloHandler)
    	e.GET("/comment",commentHandler)
    }
    
  • routers/routers.go

    package routers
    
    import "github.com/gin-gonic/gin"
    
    type Option func(engine *gin.Engine)
    
    
    var options = []Option{}
    
    // 注册app路由配置
    func Include(opts ...Option) {
    	options = append(options, opts...)
    }
    //初始化操作
    func Init() *gin.Engine {
    	r := gin.New()
    	for _, opt := range options{
    		opt(r)
    	}
    	return r
    }
    
    
    
  • main.go

    package main
    
    import (
    	"fmt"
    	"gin_demo/app/blog"
    	"gin_demo/app/shop"
    	"gin_demo/routers"
    )
    
    func main() {
    	// 家在多个APP的路由配置
    	routers.Include(shop.Routers,blog.Routers)
    	// 初始化路由
    	r := routers.Init()
    	if err := r.Run(); err != nil {
    		fmt.Println("startup service failed, err:%v
    ", err)
    	}
    }
    
原文地址:https://www.cnblogs.com/xujunkai/p/13341737.html