Gin 路由注册与请求参数获取

Gin 路由注册与请求参数获取

一、Web应用开发的两种模式

1.前后端不分离模式

  • 也叫前后端混合开发模式, 需要后端写模板语言(dtl), 返回的是HTML页面
  • 浏览器 : 请求动态页面
  • 后端 : 返回HTML
  • 优点:可以直接渲染页面, 方便处理请求数据

  • 缺点:耦合度非常高, 不方便扩展

2.前后端分离模式

  • 前端 : 只写前端
  • 后端 : 只专注于写后端接口, 返回 json, xml格式数据
  • 流程 :

浏览器到静态文件服务器请求静态页面, 静态服务器返回静态页面

JS 请求达到后端, 后端再返回 JSON 或 XML格式的数据

  • 优点
  • 不需要管前端怎么实现, 后端开发者需要做的就是写接口
  • 只需要知道, 你前端传过来什么, 然后需要后端这边传回去什么就行了
  • 主要的就是操作逻辑, 解耦合性高
  • 缺点
  • 程序员不知道前端的具体流程, 然后对表的设计, 对业务或许就理解的没有那么透彻
  • 还存在前后端联调各种问题, 前端和后端的沟通等

二、RESTful介绍

RESTful(Representational State Transfer)代表的是一种基于HTTP协议设计的软件架构风格,它通常用于构建Web服务,是Representational State Transfer的简称,中文翻译为"表征状态转移"或"表现层状态转化"。RESTful架构的设计理念是将资源表示为URI(统一资源标识符),通过HTTP协议的GET、POST、PUT、DELETE等方法对资源进行操作。以下是RESTful架构的一些关键特点:

  1. 资源(Resource):在RESTful架构中,所有的数据或服务都被抽象为资源,每个资源都有一个唯一的标识符(URI)。
  2. 表现层(Representation):资源的表现层是指资源在不同的表示形式之间进行切换,通常使用JSON或XML格式。客户端和服务器之间通过资源的表现层进行通信。
  3. 状态转移(State Transfer):RESTful架构通过HTTP方法(GET、POST、PUT、DELETE等)实现状态的转移,对资源进行增删改查的操作。
  4. 无状态(Stateless):RESTful服务是无状态的,每个请求都包含足够的信息,使服务器能够理解和处理请求,而无需依赖之前的请求。

三、API接口

3.1 RESTful API设计指南

参考资料 阮一峰 理解RESTful架构

3.2 API与用户的通信协议

总是使用HTTPs协议

3.3 RestFul API接口设计规范

3.3.1 api接口

  • 规定了前后台信息交互规则的url链接,也就是前后台信息交互的媒介

3.3.2 接口文档:

  • 可以手动写(公司有平台,录到平台里)
  • 自动生成(coreapi,swagger)

3.4 restful规范(10条,规定了这么做,公司可以不采用)

  1. 数据的安全保障,通常使用https进行传输

  2. 域名中会含有API标识

    api.example.com 尽量将API部署在专用域名

    https://127.0.0.0:8080/api/ API很简单

  3. 请求地址中带版本信息,或者在请求头中

    https://127.0.0.0:8080/api/v1/

  4. 任何东西都是资源,均使用名词表示 (尽量不要用动词)

    api.example.com/v1/books/

    api.example.com/v1/get_all_...

  5. 请求方式区分不同操作

    • get获取:从服务器取出资源(一项或多项)

    • post新增数据:在服务器新建一个资源

    • put/patch:patch是局部更新,put是全部(基本上更新都用put)

    • delete:从服务器中删除

  6. 在请求路径中带过滤,通过在url上传参的形式传递搜索条件

    api.example.com/v1/?name='金...

    api.example.com/v1/name?sor...

    api.example.com/v1/zoos?lim...

    api.example.com/v1/zoos?off...

    api.example.com/v1/zoos?pag...

    api.example.com/v1/zoos?sor...

    api.example.com/v1/zoos?ani...

  7. 返回数据中带状态码

    • http请求的状态码

    • 返回的json格式中到状态码(标志当次请求成功或失败)

      python 复制代码
      200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
      201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
      202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
      204 NO CONTENT - [DELETE]:用户删除数据成功。
      400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
      401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
      403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
      404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
      406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
      410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
      422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
      500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

      更多状态码参考:http://tools.jb51.net/table/http_status_code

  8. 返回数据中带错误信息

    • 错误处理,应返回错误信息,error当做key

      python 复制代码
      {
          error: "Invalid API key"
      }
  9. 对不同操作,返回数据符合如下规范(这只是规范)

    python 复制代码
    GET /books:返回资源对象的列表(数组)[{},{},{}]
    GET /books/1:返回单个资源对象    {}
    POST /books:返回新生成的资源对象  {新增的书}
    PUT /books/1:返回完整的资源对象   {返回修改后的}
    PATCH /books/1: 返回完整的资源对象  {返回修改后的}
    DELETE /books/1:  返回一个空文档           
    
    {status:100,msg:查询成功,data:null}
  10. 返回结果中带连接

    RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

    python 复制代码
    {"link": {
      "rel":   "collection https://www.example.com/zoos",
      "href":  "https://api.example.com/zoos",
      "title": "List of zoos",
      "type":  "application/vnd.yourformat+json"
    }}

四、图书管理系统设计

例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:

请求方法 URL 含义
GET /book 查询书籍信息
POST /create_book 创建书籍记录
POST /update_book 更新书籍信息
POST /delete_book 删除书籍信息

同样的需求我们按照RESTful API设计如下:

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

新建一个book.go文件,键入如下代码:

go 复制代码
package main

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

func main() {
	r := gin.Default()
	r.GET("/book", func(c *gin.Context) {
		c.String(http.StatusOK, "查询书籍信息")
	})

	r.POST("/book", func(c *gin.Context) {
		c.String(http.StatusOK, "新增书籍信息")
	})

	r.PUT("/book", func(c *gin.Context) {
		c.String(http.StatusOK, "修改书籍信息")
	})

	r.DELETE("/book", func(c *gin.Context) {
		c.String(http.StatusOK, "删除书籍信息")
	})
	r.Run(":8080")
}

接下来我们可以使用Postman来作为客户端的来调用我们刚刚写好的接口。

五、Gin 路由类型

Gin 支持很多类型的路由:

  • 静态路由:完全匹配的路由,也就是前面 我们注册的 hello 的路由。
  • 参数路由:在路径中带上了参数的路由。
  • 通配符路由:任意匹配的路由。

通配符路由

通配符路由究竟匹配上了什么,也是通过 Param 方法获得的。

通配符路由不能注册这种 /users/*/users/*/a。也就是说,* 不能单独出现。

六、路由参数

6.1 获取URL后面的参数

  • URL参数可以通过DefaultQuery()Query()方法获取
  • DefaultQuery()若参数不存在则返回默认值,Query()若不存在,返回空串
  • 指的是URL中?后面携带的参数,例如:/user/search?username=贾维斯&address=北京
go 复制代码
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()
}

6.2 获取path参数

请求的参数通过URL路径传递,例如:/user/search/贾维斯/北京。在Gin框架中,提供了c.Param方法可以获取路径中的参数。 获取请求URL路径中的参数的方式如下。

go 复制代码
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")
}

6.3 取JSON参数

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

go 复制代码
package main

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

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

		c.JSON(http.StatusOK, m)
	})
	r.Run(":8080")
}

七、路由组

在Gin框架中,路由组是一种用于组织和管理路由的机制。路由组可以帮助开发者更好地组织代码,提高可读性,并且能够对一组路由应用相同的中间件。以下是关于路由组的介绍:

7.1 普通路由

普通路由是指直接注册在Gin引擎上的路由,这些路由没有被分组,是独立存在的。下面是一个普通路由的简单例子:

go 复制代码
package main

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

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

	router.GET("/hello", func(c *gin.Context) {
		c.String(http.StatusOK, "Hello, Gin!")
	})

	router.GET("/world", func(c *gin.Context) {
		c.String(http.StatusOK, "World, Gin!")
	})

	router.Run(":8080")
}

上述例子中,/hello/world 是两个独立的普通路由。

7.2 路由组

路由组通过Group方法创建,可以将一组相关的路由放到同一个路由组中。通过路由组,可以更好地组织代码和应用中间件。以下是一个简单的路由组示例:

go 复制代码
package main

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

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

	// 创建一个路由组
	apiGroup := router.Group("/api")

	// 在路由组中注册路由
	apiGroup.GET("/users", func(c *gin.Context) {
		c.String(http.StatusOK, "Get Users")
	})

	apiGroup.POST("/users", func(c *gin.Context) {
		c.String(http.StatusOK, "Create User")
	})

	router.Run(":8080")
}

上述例子中,/api 是一个路由组,包含了两个路由 /users(GET和POST)。这样,相同业务功能的路由被组织在一起,提高了代码的可读性和可维护性。

八、重定向

8.1 HTTP重定向

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

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

8.2 路由重定向

路由重定向,使用HandleContext

go 复制代码
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"})
})

九、请求参数绑定

在Gin框架中,请求参数绑定是一种常见的操作,它允许你从HTTP请求中提取参数并将其绑定到Go语言结构体中。这样可以更方便地处理请求数据。以下是关于请求参数绑定的一些建议和示例:

9.1 获取查询参数

你可以使用c.Queryc.DefaultQuery方法来获取URL中的查询参数。

go 复制代码
package main

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

type QueryParams struct {
	Name  string `form:"name"`
	Age   int    `form:"age"`
}

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

	router.GET("/user", func(c *gin.Context) {
		var queryParams QueryParams

		// 使用 c.ShouldBindQuery 绑定查询参数到结构体
		if err := c.ShouldBindQuery(&queryParams); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"name": queryParams.Name,
				"age":  queryParams.Age,
			})
		} else {
			c.String(http.StatusBadRequest, "参数绑定失败")
		}
	})

	router.Run(":8080")
}

上述例子中,通过c.ShouldBindQuery将查询参数绑定到QueryParams结构体中,然后使用这个结构体处理请求。

9.2 获取表单数据

使用c.ShouldBindc.ShouldBindJSON方法可以将POST请求的表单数据或JSON数据绑定到结构体中。

go 复制代码
package main

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

type FormData struct {
	Name string `form:"name"`
	Age  int    `form:"age"`
}

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

	router.POST("/user", func(c *gin.Context) {
		var formData FormData

		// 使用 c.ShouldBind 绑定表单数据到结构体
		if err := c.ShouldBind(&formData); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"name": formData.Name,
				"age":  formData.Age,
			})
		} else {
			c.String(http.StatusBadRequest, "参数绑定失败")
		}
	})

	router.Run(":8080")
}

在上述例子中,c.ShouldBind将表单数据绑定到FormData结构体中。

十、小黄书起步:Web 接口之用户模块设计

10.1 用户模块分析

我们现在要设计一个用户模块,对于一个用户模块来说,最先要设计的接口就是:注册和登录。而后要考虑提供:编辑和查看用户信息。同样的需求我们按照RESTful API设计如下:

请求方法 URL 含义
GET /users/profile 查询用户信息
POST /users/signup 用户登录
POST /users/login 用户注册
POST /users/edit 编辑用户信息

首先,我们创建一个webook目录,并且初始化go mod

bash 复制代码
mkdir webook
go mod init webook

10.2 目录结构

项目目录结构如图:

在 webook 顶级目录下有:

  • main 文件,用于启动 webook。
  • 一个 internal 包,里面放着的就是我们所有的业务 代码。
  • 一个 pkg 包,这是我们用于存放公共库和包。

10.3 Handler 的用途

接着我们在user.go 中直接定义了一个 UserHandler,然后将所有 和用户有关的路由都定义在了这个 Handler 上,同时,也定义了一个 RegisterRoutes 的方法,用来注册路由。这里用定义在 UserHandler 上的方法来作为对应路由的处理逻辑。

10.4 用分组路由来简化注册

你可以注意到,就是我们所有的路由都有 /users 这个前缀,要是手一抖就有可能写错,这时候可以考虑使用 Gin 的分组路由功能,修改后如下:

10.5 接收请求数据:接收请求结构体

一般来说,我们都是定义一个结构体来接受数据。这里我们使用了方法内部类 SignUpRequest 来接收数据。

10.6 接收请求数据:Bind 方法

Bind 方法是 Gin 里面最常用的用于接收请求的方法。

Bind 方法会根据 HTTP 请求的 Content-Type 来决定怎么处理。

比如我们的请求是 JSON 格式,Content-Typeapplication/json,那么 Gin 就会使用 JSON 来反序列化。

如果 Bind 方法发现输入有问题,它就会直接返回一个错误响应到前端。

10.7 校验请求:正则表达式

在我们这个注册的业务里面,校验分为如下:

  • 邮箱需要符合一定的格式:也就是账号这里,必须是一个合法的邮箱。
  • 密码和确认密码需要相等:这是为了确保用户没有输错。
  • 密码需要符合一定的规律:要求用户输入的密码必须不少于八位,必须要包含数字、特殊字符。

综上所述,我们用正则表达式来校验请求,正则表达式是一种用于匹配和操作文本的强大工 具,它是由一系列字符和特殊字符组成的模式,用 于描述要匹配的文本模式。正则表达式可以在文本中查找、替换、提取和验证 特定的模式。代码如图:

10.8 校验请求:预编译正则表达式

我们可以预编译正则表达式来提高校验速度。

10.9 校验请求:Go 正则表达式不支持部分语法

前面我们用的是官方自带的,但是 Go 自带的正 则表达式不支持一些语法,比如说我这里想要用 的表达式:^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$

类似于 ?=. 这种就不支持。所以我们换用另外一个开源的正则表达式匹配 库:github.com/dlclark/regexp2

10.10 校验请求:全部校验

整体的校验如图,注意我们区分了不同的错误,返回了不同的错误提示。

最后,完整代码如下:

user.go 文件

go 复制代码
package web

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

type UserHandler struct {
	emailExp    *regexp.Regexp
	passwordExp *regexp.Regexp
}

func NewUserHandler() *UserHandler {
	const (
		emailRegexPattern    = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"
		passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$`
	)
	emailExp := regexp.MustCompile(emailRegexPattern, regexp.None)
	passwordExp := regexp.MustCompile(passwordRegexPattern, regexp.None)
	return &UserHandler{
		emailExp:    emailExp,
		passwordExp: passwordExp,
	}
}

func (u *UserHandler) RegisterRoutes(server *gin.Engine) {
	ug := server.Group("/user")   //ug is user group
	ug.GET("/profile", u.Profile) // 查询用户信息接口
	ug.POST("/signup", u.SignUp)  // 注册接口
	ug.POST("/login", u.Login)    // 登录接口
	ug.POST("/logout", u.Logout)  // 登出接口
	ug.POST("/edit", u.Edit)      // 修改用户信息接口

}

func (u *UserHandler) RegisterRoutesV1(ug *gin.RouterGroup) {
	ug.GET("/profile", u.Profile) // 查询用户信息接口
	ug.POST("/signup", u.SignUp)  // 注册接口
	ug.POST("/login", u.Login)    // 登录接口
	ug.POST("/logout", u.Logout)  // 登出接口
	ug.POST("/edit", u.Edit)      // 修改用户信息接口

}
func (u *UserHandler) Profile(ctx *gin.Context) {
}
func (u *UserHandler) SignUp(ctx *gin.Context) {
	type SignUpRequest struct {
		Email           string `json:"email"`
		Password        string `json:"password"`
		ConfirmPassword string `json:"confirmPassword"`
	}
	var request SignUpRequest
	// 如果 Bind 方法发现输入有问题,它就会直接返回一 个错误响应到前端。
	if err := ctx.Bind(&request); err != nil {
		return
	}

	ok, err := u.emailExp.MatchString(request.Email)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	if !ok {
		ctx.String(http.StatusOK, "邮箱格式错误")
		return
	}
	ok, err = u.passwordExp.MatchString(request.Password)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	if !ok {
		ctx.String(http.StatusOK, "密码必须包含至少一个数字、一个字母、一个特殊字符,并且长度至少为8位")
		return
	}
	if request.Password != request.ConfirmPassword {
		ctx.String(http.StatusOK, "两次密码不一致")
		return
	}
	ctx.String(http.StatusOK, "注册成功")
	fmt.Printf("请求体为:%v", request)
}
func (u *UserHandler) Login(ctx *gin.Context) {

}

func (u *UserHandler) Logout(ctx *gin.Context) {

}
func (u *UserHandler) Edit(ctx *gin.Context) {

}

main.go 文件:

go 复制代码
package main

import (
	"github.com/gin-gonic/gin"
	"strings"
	"time"
	"webook/internal/web"
)

func main() {
	server := gin.Default()
	u := web.NewUserHandler()
	u.RegisterRoutes(server)
	//ug := server.Group("/user/v1") //ug is user group
	//c.RegisterRoutesV1(ug)
	server.Run(":8080")
}

最后,我们通过postman 请求接口:http://127.0.0.1:8080/user/signup/

相关推荐
蒙娜丽宁2 小时前
深入理解Go语言中的接口定义与使用
开发语言·后端·golang·go
蒙娜丽宁3 天前
Go语言错误处理详解
ios·golang·go·xcode·go1.19
qq_172805594 天前
GO Govaluate
开发语言·后端·golang·go
littleschemer5 天前
Go缓存系统
缓存·go·cache·bigcache
程序者王大川5 天前
【GO开发】MacOS上搭建GO的基础环境-Hello World
开发语言·后端·macos·golang·go
Grassto6 天前
Gitlab 中几种不同的认证机制(Access Tokens,SSH Keys,Deploy Tokens,Deploy Keys)
go·ssh·gitlab·ci
高兴的才哥6 天前
kubevpn 教程
kubernetes·go·开发工具·telepresence·bridge to k8s
少林码僧7 天前
sqlx1.3.4版本的问题
go
蒙娜丽宁7 天前
Go语言结构体和元组全面解析
开发语言·后端·golang·go
蒙娜丽宁7 天前
深入解析Go语言的类型方法、接口与反射
java·开发语言·golang·go