网站首页 > 技术文章 正文
Gin 入门
Gin 的学习要点
- 如何定义路由:包括参数路由、通配符路由。
- 如何处理输入输出。
- 如何使用 middleware 解决 AOP 问题。
中文官网:https://gin-gonic.com/zh-cn/docs/
最简Gin应用
在 Gin 框架中,Engine可以用来监听一个端口,在逻辑上代表一个服务器。一个 Go 进程可以创建多个Engine。
对于最简的 “Hello, world” 应用步骤如下:
- 在应用中引入 Gin 依赖:通过go get github.com/gin-gonic/gin@latest命令获取最新版本的 Gin 依赖。
- 初始化一个Engine。
- 调用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 方法都提供了路由注册方法。这些方法基本上都有两个参数:
- 路由规则,例如 “/hello” 这样的静态路由。
- 处理函数,即开发者注册的用于处理请求并返回响应的方法,比如返回 “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 框架支持多种类型的路由:
- 静态路由:完全匹配的路由,例如前面注册的 “hello” 路由。
- 参数路由:路径中带有参数的路由。
- 通配符路由:可以进行任意匹配的路由。
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 方法发现输入有问题,它会直接返回一个错误响应到前端。
校验请求总结:
校验请求的内容通常由公司产品经理确定。在注册业务中,校验分为两块:
- 邮箱需要符合一定格式,即账号必须是合法邮箱。
- 密码和确认密码需要相等以确保用户没有输错,并且密码需要符合一定规律,如不少于八位且包含数字、特殊字符。如今密码强度虽重要但更强调通过二次验证保证登录安全性。
同时提出一个问题:能否让前端进行校验。
(不管前端有没有校验,后端都要校验。)
校验请求之正则表达式总结:
校验请求可以使用正则表达式。正则表达式是一种强大的用于匹配和操作文本的工具,由一系列字符和特殊字符组成的模式来描述要匹配的文本模式,可以在文本中进行查找、替换、提取和验证特定模式。
如果校验规则很复杂,可以使用多个校验表达式进行多次校验。
校验请求与 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)
}后续下期更新...
猜你喜欢
- 2024-11-02 武汉课工场大数据培训:Java正则表达式入坑指南
- 2024-11-02 Go语言进阶之路:并发爬虫,爬取空姐网所有相册图片
- 2024-11-02 golang常用库:gorilla/mux-http路由库使用
- 2024-11-02 golang 使用pprof和go-torch做性能分析
- 2024-11-02 日志文件转运工具Filebeat笔记(日志转载)
- 2024-11-02 Linux 命令行下搜索工具大盘点,效率提高不止一倍
- 2024-11-02 SlimTrie:战胜Btree单机百亿文件的极致索引-实现篇
- 2024-11-02 Go的安全编程和防御性编程(输入验证和过滤)
- 2024-11-02 清华学神尹成带你学习golang2021(56)(Go语言测试命令)
- 2024-11-02 Go 1.9版本即将到来,众多新功能一览
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)