ToB企服应用市场:ToB评测及商务社交产业平台

标题: Gin 路由注册与请求参数获取 [打印本页]

作者: 不到断气不罢休    时间: 2024-3-20 08:24
标题: Gin 路由注册与请求参数获取
Gin 路由注册与请求参数获取


目录

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

1.前后端不分离模式


2.前后端分离模式

浏览器到静态文件服务器请求静态页面, 静态服务器返回静态页面
JS 请求达到后端, 后端再返回 JSON 或 XML格式的数据




二、RESTful介绍

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

3.1 RESTful API设计指南

参考资料 阮一峰 理解RESTful架构
3.2 API与用户的通信协议

总是使用HTTPs协议
3.3 RestFul API接口设计规范

3.3.1 api接口

3.3.2 接口文档:

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

四、图书管理系统设计

例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们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文件,键入如下代码:
  1. package main
  2. import (
  3.         "github.com/gin-gonic/gin"
  4.         "net/http"
  5. )
  6. func main() {
  7.         r := gin.Default()
  8.         r.GET("/book", func(c *gin.Context) {
  9.                 c.String(http.StatusOK, "查询书籍信息")
  10.         })
  11.         r.POST("/book", func(c *gin.Context) {
  12.                 c.String(http.StatusOK, "新增书籍信息")
  13.         })
  14.         r.PUT("/book", func(c *gin.Context) {
  15.                 c.String(http.StatusOK, "修改书籍信息")
  16.         })
  17.         r.DELETE("/book", func(c *gin.Context) {
  18.                 c.String(http.StatusOK, "删除书籍信息")
  19.         })
  20.         r.Run(":8080")
  21. }
复制代码
接下来我们可以使用Postman来作为客户端的来调用我们刚刚写好的接口。
五、Gin 路由类型

Gin 支持很多类型的路由:

通配符路由

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


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

6.1 获取URL后面的参数

  1. func main() {
  2.         //Default返回一个默认的路由引擎
  3.         r := gin.Default()
  4.         r.GET("/user/search", func(c *gin.Context) {
  5.                 username := c.DefaultQuery("username", "贾维斯")
  6.                 //username := c.Query("username")
  7.                 address := c.Query("address")
  8.                 //输出json结果给调用方
  9.                 c.JSON(http.StatusOK, gin.H{
  10.                         "message":  "ok",
  11.                         "username": username,
  12.                         "address":  address,
  13.                 })
  14.         })
  15.         r.Run()
  16. }
复制代码

6.2 获取path参数

请求的参数通过URL路径传递,例如:/user/search/贾维斯/北京。在Gin框架中,提供了c.Param方法可以获取路径中的参数。 获取请求URL路径中的参数的方式如下。
  1. func main() {
  2.         //Default返回一个默认的路由引擎
  3.         r := gin.Default()
  4.         r.GET("/user/search/:username/:address", func(c *gin.Context) {
  5.                 username := c.Param("username")
  6.                 address := c.Param("address")
  7.                 //输出json结果给调用方
  8.                 c.JSON(http.StatusOK, gin.H{
  9.                         "message":  "ok",
  10.                         "username": username,
  11.                         "address":  address,
  12.                 })
  13.         })
  14.         r.Run(":8080")
  15. }
复制代码
6.3 取JSON参数

当前端请求的数据通过JSON提交时,例如向/json发送一个JSON格式的POST请求,则获取请求参数的方式如下:
  1. package main
  2. import (
  3.         "encoding/json"
  4.         "fmt"
  5.         "github.com/gin-gonic/gin"
  6.         "net/http"
  7. )
  8. func main() {
  9.         r := gin.Default()
  10.         r.POST("/json", func(c *gin.Context) {
  11.                 // 注意:下面为了举例子方便,暂时忽略了错误处理
  12.                 b, _ := c.GetRawData() // 从c.Request.Body读取请求数据
  13.                 fmt.Printf("raw data: %s\n", string(b))
  14.                 // 定义map或结构体
  15.                 var m map[string]interface{}
  16.                 // 反序列化
  17.                 _ = json.Unmarshal(b, &m)
  18.                 c.JSON(http.StatusOK, m)
  19.         })
  20.         r.Run(":8080")
  21. }
复制代码
七、路由组

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

普通路由是指直接注册在Gin引擎上的路由,这些路由没有被分组,是独立存在的。下面是一个普通路由的简单例子:
  1. package main
  2. import (
  3.         "github.com/gin-gonic/gin"
  4.         "net/http"
  5. )
  6. func main() {
  7.         router := gin.Default()
  8.         router.GET("/hello", func(c *gin.Context) {
  9.                 c.String(http.StatusOK, "Hello, Gin!")
  10.         })
  11.         router.GET("/world", func(c *gin.Context) {
  12.                 c.String(http.StatusOK, "World, Gin!")
  13.         })
  14.         router.Run(":8080")
  15. }
复制代码
上述例子中,/hello 和 /world 是两个独立的普通路由。
7.2 路由组

路由组通过Group方法创建,可以将一组相关的路由放到同一个路由组中。通过路由组,可以更好地组织代码和应用中间件。以下是一个简单的路由组示例:
  1. package main
  2. import (
  3.         "github.com/gin-gonic/gin"
  4.         "net/http"
  5. )
  6. func main() {
  7.         router := gin.Default()
  8.         // 创建一个路由组
  9.         apiGroup := router.Group("/api")
  10.         // 在路由组中注册路由
  11.         apiGroup.GET("/users", func(c *gin.Context) {
  12.                 c.String(http.StatusOK, "Get Users")
  13.         })
  14.         apiGroup.POST("/users", func(c *gin.Context) {
  15.                 c.String(http.StatusOK, "Create User")
  16.         })
  17.         router.Run(":8080")
  18. }
复制代码
上述例子中,/api 是一个路由组,包含了两个路由 /users(GET和POST)。这样,相同业务功能的路由被组织在一起,提高了代码的可读性和可维护性。
八、重定向

8.1 HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。
  1. r.GET("/test", func(c *gin.Context) {
  2.         c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/")
  3. })
复制代码
8.2 路由重定向

路由重定向,使用HandleContext:
  1. r.GET("/test", func(c *gin.Context) {
  2.     // 指定重定向的URL
  3.     c.Request.URL.Path = "/test2"
  4.     r.HandleContext(c)
  5. })
  6. r.GET("/test2", func(c *gin.Context) {
  7.     c.JSON(http.StatusOK, gin.H{"hello": "world"})
  8. })
复制代码
九、请求参数绑定

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

你可以使用c.Query或c.DefaultQuery方法来获取URL中的查询参数。
  1. package main
  2. import (
  3.         "github.com/gin-gonic/gin"
  4.         "net/http"
  5. )
  6. type QueryParams struct {
  7.         Name  string `form:"name"`
  8.         Age   int    `form:"age"`
  9. }
  10. func main() {
  11.         router := gin.Default()
  12.         router.GET("/user", func(c *gin.Context) {
  13.                 var queryParams QueryParams
  14.                 // 使用 c.ShouldBindQuery 绑定查询参数到结构体
  15.                 if err := c.ShouldBindQuery(&queryParams); err == nil {
  16.                         c.JSON(http.StatusOK, gin.H{
  17.                                 "name": queryParams.Name,
  18.                                 "age":  queryParams.Age,
  19.                         })
  20.                 } else {
  21.                         c.String(http.StatusBadRequest, "参数绑定失败")
  22.                 }
  23.         })
  24.         router.Run(":8080")
  25. }
复制代码
上述例子中,通过c.ShouldBindQuery将查询参数绑定到QueryParams结构体中,然后使用这个结构体处理请求。
9.2 获取表单数据

使用c.ShouldBind或c.ShouldBindJSON方法可以将POST请求的表单数据或JSON数据绑定到结构体中。
  1. package main
  2. import (
  3.         "github.com/gin-gonic/gin"
  4.         "net/http"
  5. )
  6. type FormData struct {
  7.         Name string `form:"name"`
  8.         Age  int    `form:"age"`
  9. }
  10. func main() {
  11.         router := gin.Default()
  12.         router.POST("/user", func(c *gin.Context) {
  13.                 var formData FormData
  14.                 // 使用 c.ShouldBind 绑定表单数据到结构体
  15.                 if err := c.ShouldBind(&formData); err == nil {
  16.                         c.JSON(http.StatusOK, gin.H{
  17.                                 "name": formData.Name,
  18.                                 "age":  formData.Age,
  19.                         })
  20.                 } else {
  21.                         c.String(http.StatusBadRequest, "参数绑定失败")
  22.                 }
  23.         })
  24.         router.Run(":8080")
  25. }
复制代码
在上述例子中,c.ShouldBind将表单数据绑定到FormData结构体中。
十、小黄书起步:Web 接口之用户模块设计

10.1 用户模块分析

我们现在要设计一个用户模块,对于一个用户模块来说,最先要设计的接口就是:注册和登录。而后要考虑提供:编辑和查看用户信息。同样的需求我们按照RESTful API设计如下:
请求方法URL含义GET/users/profile查询用户信息POST/users/signup用户登录POST/users/login用户注册POST/users/edit编辑用户信息首先,我们创建一个webook目录,并且初始化go mod
  1. mkdir webook
  2. go mod init webook
复制代码
10.2 目录结构

项目目录结构如图:

在 webook 顶级目录下有:
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-Type 是 application/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 文件
  1. package web
  2. import (
  3.         "fmt"
  4.         regexp "github.com/dlclark/regexp2"
  5.         "github.com/gin-gonic/gin"
  6.         "net/http"
  7. )
  8. type UserHandler struct {
  9.         emailExp    *regexp.Regexp
  10.         passwordExp *regexp.Regexp
  11. }
  12. func NewUserHandler() *UserHandler {
  13.         const (
  14.                 emailRegexPattern    = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"
  15.                 passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$`
  16.         )
  17.         emailExp := regexp.MustCompile(emailRegexPattern, regexp.None)
  18.         passwordExp := regexp.MustCompile(passwordRegexPattern, regexp.None)
  19.         return &UserHandler{
  20.                 emailExp:    emailExp,
  21.                 passwordExp: passwordExp,
  22.         }
  23. }
  24. func (u *UserHandler) RegisterRoutes(server *gin.Engine) {
  25.         ug := server.Group("/user")   //ug is user group
  26.         ug.GET("/profile", u.Profile) // 查询用户信息接口
  27.         ug.POST("/signup", u.SignUp)  // 注册接口
  28.         ug.POST("/login", u.Login)    // 登录接口
  29.         ug.POST("/logout", u.Logout)  // 登出接口
  30.         ug.POST("/edit", u.Edit)      // 修改用户信息接口
  31. }
  32. func (u *UserHandler) RegisterRoutesV1(ug *gin.RouterGroup) {
  33.         ug.GET("/profile", u.Profile) // 查询用户信息接口
  34.         ug.POST("/signup", u.SignUp)  // 注册接口
  35.         ug.POST("/login", u.Login)    // 登录接口
  36.         ug.POST("/logout", u.Logout)  // 登出接口
  37.         ug.POST("/edit", u.Edit)      // 修改用户信息接口
  38. }
  39. func (u *UserHandler) Profile(ctx *gin.Context) {
  40. }
  41. func (u *UserHandler) SignUp(ctx *gin.Context) {
  42.         type SignUpRequest struct {
  43.                 Email           string `json:"email"`
  44.                 Password        string `json:"password"`
  45.                 ConfirmPassword string `json:"confirmPassword"`
  46.         }
  47.         var request SignUpRequest
  48.         // 如果 Bind 方法发现输入有问题,它就会直接返回一 个错误响应到前端。
  49.         if err := ctx.Bind(&request); err != nil {
  50.                 return
  51.         }
  52.         ok, err := u.emailExp.MatchString(request.Email)
  53.         if err != nil {
  54.                 ctx.String(http.StatusOK, "系统错误")
  55.                 return
  56.         }
  57.         if !ok {
  58.                 ctx.String(http.StatusOK, "邮箱格式错误")
  59.                 return
  60.         }
  61.         ok, err = u.passwordExp.MatchString(request.Password)
  62.         if err != nil {
  63.                 ctx.String(http.StatusOK, "系统错误")
  64.                 return
  65.         }
  66.         if !ok {
  67.                 ctx.String(http.StatusOK, "密码必须包含至少一个数字、一个字母、一个特殊字符,并且长度至少为8位")
  68.                 return
  69.         }
  70.         if request.Password != request.ConfirmPassword {
  71.                 ctx.String(http.StatusOK, "两次密码不一致")
  72.                 return
  73.         }
  74.         ctx.String(http.StatusOK, "注册成功")
  75.         fmt.Printf("请求体为:%v", request)
  76. }
  77. func (u *UserHandler) Login(ctx *gin.Context) {
  78. }
  79. func (u *UserHandler) Logout(ctx *gin.Context) {
  80. }
  81. func (u *UserHandler) Edit(ctx *gin.Context) {
  82. }
复制代码
main.go 文件:
  1. package main
  2. import (
  3.         "github.com/gin-gonic/gin"
  4.         "strings"
  5.         "time"
  6.         "webook/internal/web"
  7. )
  8. func main() {
  9.         server := gin.Default()
  10.         u := web.NewUserHandler()
  11.         u.RegisterRoutes(server)
  12.         //ug := server.Group("/user/v1") //ug is user group
  13.         //c.RegisterRoutesV1(ug)
  14.         server.Run(":8080")
  15. }
复制代码
最后,我们通过postman 请求接口:http://127.0.0.1:8080/user/signup/


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4