Golang Gin 初学者指南(一)

发表时间: 2024-10-29 19:40

Gin 入门

Gin 的学习要点

  1. 如何定义路由:包括参数路由、通配符路由。
  2. 如何处理输入输出。
  3. 如何使用 middleware 解决 AOP 问题。

中文官网:https://gin-gonic.com/zh-cn/docs/

最简Gin应用

在 Gin 框架中,Engine可以用来监听一个端口,在逻辑上代表一个服务器。一个 Go 进程可以创建多个Engine

对于最简的 “Hello, world” 应用步骤如下:

  1. 在应用中引入 Gin 依赖:通过go get github.com/gin-gonic/gin@latest命令获取最新版本的 Gin 依赖。
  2. 初始化一个Engine
  3. 调用Run方法监听端口 8080 并启动服务。
package mainimport (    "github.com/gin-gonic/gin"    "log"    "net/http")func main() {    engine := gin.Default()    engine.GET("/index", func(context *gin.Context) {        context.String(http.StatusOK, "hello world")    })    if err := engine.Run(":8080"); err != nil {        log.Printf("Listen err: %v\n", err)    }}

gin.Engine

在 Gin 框架中,一个 Web 服务器被抽象为gin.Engine。在一个应用中可以创建多个Engine实例,用于监听不同的端口。

Engine承担着重要的核心职责,包括路由注册以及接入 middleware(中间件)。而RouterGroup是实现路由功能的核心组件,Engine组合了RouterGroup

type Engine struct {    RouterGroup    //...}    engine := gin.Default() // 创建的 *Engineengine.GET("/index", func(context *gin.Context) {        context.String(http.StatusOK, "hello world")    })

gin.Context

在 Gin 框架中,gin.Context是核心类型。在日常开发中,开发者最常与之打交道。

gin.Context的核心职责是处理请求并返回响应。其中,Request代表请求,Writer代表响应。

type Context struct {    writermem responseWriter    Request   *http.Request    Writer    ResponseWriter    // ...}func(context *gin.Context) {        context.String(http.StatusOK, "hello world")    }

路由注册

在 Gin 框架中,为每一个 HTTP 方法都提供了路由注册方法。这些方法基本上都有两个参数:

  1. 路由规则,例如 “/hello” 这样的静态路由。
  2. 处理函数,即开发者注册的用于处理请求并返回响应的方法,比如返回 “hello, world” 的方法。
// POST is a shortcut for router.Handle("POST", path, handlers).func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {    return group.handle(http.MethodPost, relativePath, handlers)}// GET is a shortcut for router.Handle("GET", path, handlers).func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {    return group.handle(http.MethodGet, relativePath, handlers)}// DELETE is a shortcut for router.Handle("DELETE", path, handlers).func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {    return group.handle(http.MethodDelete, relativePath, handlers)}// PATCH is a shortcut for router.Handle("PATCH", path, handlers).func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {    return group.handle(http.MethodPatch, relativePath, handlers)}// PUT is a shortcut for router.Handle("PUT", path, handlers).func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {    return group.handle(http.MethodPut, relativePath, handlers)}// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handlers).func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {    return group.handle(http.MethodOptions, relativePath, handlers)}// HEAD is a shortcut for router.Handle("HEAD", path, handlers).func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {    return group.handle(http.MethodHead, relativePath, handlers)}// Any registers a route that matches all the HTTP methods.// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.

Gin 路由

Gin 框架支持多种类型的路由:

  1. 静态路由:完全匹配的路由,例如前面注册的 “hello” 路由。
  2. 参数路由:路径中带有参数的路由。
  3. 通配符路由:可以进行任意匹配的路由。
package mainimport (    "github.com/gin-gonic/gin"    "log"    "net/http")func main() {    engine := gin.Default()    // 静态路由    engine.GET("/index", func(context *gin.Context) {        context.String(http.StatusOK, "hello world")    })    //参数路由    engine.GET("/user/:name", func(context *gin.Context) {        name := context.Param("name")        context.String(http.StatusOK, "这是你传过来的名字 %s", name)    })    // 通配符匹配    engine.GET("/views/*.html", func(context *gin.Context) {        path := context.Param(".html")        context.String(http.StatusOK, "匹配的值是 %s", path)    })    // 127.0.0.1:8080/query?id=10    engine.GET("/query", func(context *gin.Context) {        id := context.Query("id")        context.String(http.StatusOK, "查询的id是 %s", id)    })    if err := engine.Run(":8080"); err != nil {        log.Printf("Listen err: %v\n", err)    }}

案例

案例来自极客时间go训练营



定义用户基本接口 我们从用户模块开始起步。对于一个用户模块来说,最先要设计的接口就是:注册和登录。而后要考虑提供:编辑和查看用户信息。 在研发的时候,可以从前端往后端。即先定义 Web 接口,再去考虑后面的数据库设计之类的东西。因为以先定义数据库,可能遇到前端需要加字段的问题。

Handler 的用途总结

  • 直接定义了一个UserHandler,将所有与用户有关的路由都定义在这个Handler上,同时还定义了一个RegisterRoutes方法用来注册路由。
  • UserHandler上定义的方法作为对应路由的处理逻辑。
  • 在简单的应用中,这是一种不错的 Web 代码组织方式,例如后续可以有ArticleHandler等类似的处理不同模块的Handler
type UserHandler struct {}func (c *UserHandler) RegisterRoutes(server *gin.Engine)  {    server.POST("/users/signup",c.SignUp)    server.POST("/users/login",c.Login)    server.POST("/users/edit",c.Edit)    server.GET("/users/profile",c.Profile)}func (c *UserHandler) SignUp(context *gin.Context) {}func (c *UserHandler) Login(context *gin.Context) {}func (c *UserHandler) Edit(context *gin.Context) {}func (c *UserHandler) Profile(context *gin.Context) {}



分组注册

func (c *UserHandler) RegisterRoutes(server *gin.Engine) {    // 分组注册    up := server.Group("/users")    up.POST("/signup", c.SignUp)    up.POST("/login", c.Login)    up.POST("/edit", c.Edit)    up.GET("/profile", c.Profile)}

接收请求数据:

一般情况下,通过定义一个结构体来接收请求数据。这里使用了方法内部类SignUpReq来接收数据,这样做的优点是只有对应的SignUp方法能够使用SignUpReq,其他方法都无法使用。可以考虑将结构体定义在外面,但这与习惯和公司偏好相关。建议优先使用内部类的方式来定义接收请求数据的结构体。

func (c *UserHandler) SignUp(context *gin.Context) {    type SingUpReq struct {        Email           string `json:"email"`        ConfirmPassword string `json:"confirmPassword"`        Password        string `json:"password"`    }    var req SingUpReq    // Bind 方法会根据 Content-Type 来解析你的数据req里面    // 解析错了,就直接回写一个 400(http.StatusBadRequest)的错误. c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind)    if err := context.Bind(&req); err != nil {        return    }    context.String(http.StatusOK, "注册成功")    log.Printf("%v\n", req)}

接收请求数据之 Bind 方法总结

  • Bind 方法是 Gin 框架中最常用的接收请求的方法。
  • Bind 方法会根据 HTTP 请求的 Content-Type 来决定如何处理请求数据。例如,如果请求是 JSON 格式,Content-Type 为 application/json,那么 Gin 就会使用 JSON 来进行反序列化操作。
  • 如果 Bind 方法发现输入有问题,它会直接返回一个错误响应到前端。

校验请求总结

校验请求的内容通常由公司产品经理确定。在注册业务中,校验分为两块:

  1. 邮箱需要符合一定格式,即账号必须是合法邮箱。
  2. 密码和确认密码需要相等以确保用户没有输错,并且密码需要符合一定规律,如不少于八位且包含数字、特殊字符。如今密码强度虽重要但更强调通过二次验证保证登录安全性。

同时提出一个问题:能否让前端进行校验。

(不管前端有没有校验,后端都要校验。)

校验请求之正则表达式总结

校验请求可以使用正则表达式。正则表达式是一种强大的用于匹配和操作文本的工具,由一系列字符和特殊字符组成的模式来描述要匹配的文本模式,可以在文本中进行查找、替换、提取和验证特定模式。

如果校验规则很复杂,可以使用多个校验表达式进行多次校验。

校验请求与 Go 正则表达式及替代方案总结

Go 语言自带的正则表达式在某些情况下不支持一些语法。例如,文中提到的一个特定表达式 ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$中的类似 “?=” 这样的语法不被支持。所以为了解决这个问题,换用了一个开源的正则表达式匹配库 “github.com/dlclark/regexp2”。

func (c *UserHandler) SignUp(context *gin.Context) {    type SingUpReq struct {        Email           string `json:"email"`        ConfirmPassword string `json:"confirmPassword"`        Password        string `json:"password"`    }    var req SingUpReq    // Bind 方法会根据 Content-Type 来解析你的数据req里面    // 解析错了,就直接回写一个 400(http.StatusBadRequest)的错误. c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind)    if err := context.Bind(&req); err != nil {        return    }    const (        // 定义邮箱的正则表达式        emailRegex = `^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`        // 定义密码的正则表达式 至少8个字符    //包含大写字母    //包含小写字母    //包含数字  //包含特殊字符        passwordRegex = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$`    )        Regex, err := regexp2.Compile(emailRegex, 0)    isEmail, err := Regex.MatchString(req.Email)    if err != nil {        context.String(http.StatusInternalServerError, "系统错误 %v", err)        return    }    if !isEmail {        context.String(http.StatusUnauthorized, "邮箱校验不通过")        return    }        Regex, err = regexp2.Compile(passwordRegex, 0)    isPassword, err := Regex.MatchString(req.Password)    if err != nil {        context.String(http.StatusInternalServerError, "系统错误 %v", err)        return    }    if !isPassword {        context.String(http.StatusUnauthorized, "密码校验不通过")        return    }        if req.ConfirmPassword != req.Password {        context.String(http.StatusUnauthorized, "两次输入的密码不一致")        return    }        context.String(http.StatusOK, "注册成功")    log.Printf("%v\n", req)}

后续下期更新...