优秀的编程知识分享平台

网站首页 > 技术文章 正文

Golang Gin 入门 (一)(golang官方教程)

nanyue 2024-11-02 12:19:37 技术文章 5 ℃

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 main

import (
    "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() // 创建的 *Engine
engine.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 main

import (
    "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,}Golang Gin 入门 (一)-今日头条
// 定义密码的正则表达式 至少8个字符 //包含大写字母 //包含小写字母 //包含数字 //包含特殊字符 passwordRegex = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}Golang Gin 入门 (一)-今日头条
) 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) }

后续下期更新...



最近发表
标签列表