中文官网:https://gin-gonic.com/zh-cn/docs/
在 Gin 框架中,Engine可以用来监听一个端口,在逻辑上代表一个服务器。一个 Go 进程可以创建多个Engine。
对于最简的 “Hello, world” 应用步骤如下:
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 框架中,一个 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 框架中,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 方法都提供了路由注册方法。这些方法基本上都有两个参数:
// 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 框架支持多种类型的路由:
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 的用途总结:
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 方法总结:
校验请求总结:
校验请求的内容通常由公司产品经理确定。在注册业务中,校验分为两块:
同时提出一个问题:能否让前端进行校验。
(不管前端有没有校验,后端都要校验。)
校验请求之正则表达式总结:
校验请求可以使用正则表达式。正则表达式是一种强大的用于匹配和操作文本的工具,由一系列字符和特殊字符组成的模式来描述要匹配的文本模式,可以在文本中进行查找、替换、提取和验证特定模式。
如果校验规则很复杂,可以使用多个校验表达式进行多次校验。
校验请求与 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)}
后续下期更新...