GoWeb——Gin初识

Web本质

web是基于HTTP协议进行交互的应用网络,web就是通过使用浏览器OrApp访问各种资源。,说白了就是一个请求——处理——响应的过程。

HTTP协议

HTTP 只是规定了 TCP 连接里传输的文本格式

  • 请求(Request):浏览器发给服务器的信。
    • Method (动词)GET(我要查数据)、POST(我要提交数据)、PUT(我要修改)、DELETE(我要删除)。
    • Path (路径):比如 /user/login
    • Body (正文):当你提交表单时,数据放在这里。
  • 响应(Response):服务器回给浏览器的信。
    • Status Code (状态码)200(成功)、404(找不到)、500(服务器崩了)。
    • Body:具体的 JSON 数据或 HTML 网页。

Hello Gin

引入

Go的标准库net/http再实际开发中,想要区分请求的method需要自己写if进行判断,而且URL为/user/1001的话标准库解析起来比较费劲,在返回JSON数据需要手动处理以及设置Header,Gin的出现就是为了优化这些复杂的过程,让程序开发更关注于业务逻辑。

环境准备

1.打开项目文件夹后,打开终端

 go mod init gin_study

2.下载Gin框架

 go get -u github.com/gin-gonic/gin

我们可以发现go.mod文件多了一堆东西,那就对了喵

Hello Gin

 package main
 ​
 import (
  "net/http"
 ​
  "github.com/gin-gonic/gin"
 )
 ​
 func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
  "message": "pong",
  "status":  "success",
  })
  })
  r.Run("localhost:8080")
 }
image-20260201170605707
image-20260201170630808

核心函数解析

1.gin.Default()

初始化了一个Engin实例,它默认挂在了两个中间件:Logger(把请求日志打印到终端)和Recovery(发生了panic,拦截panic,放回500).

2.r.GET(path, handler)

通过GET方法访问/ping这个路径,并执行后面的匿名函数

Gin运行是因为他的底层不是通过正则来匹配路由而是通过Radix Tree的数据结构,用树状结构在存储数据,查找更为迅速,在复杂路由的项目中有明显的性能提升。

3.c *gin.Context

在标准库中,你需要同时操作 w http.ResponseWriter (写回数据) 和 r *http.Request (读取数据),而Gin把请求和响应都封装到了c中,里面放着这次请求的所有东西

4.c.JSON(...)

 c.JSON(http.StatusOK, gin.H{...})
  • http.StatusOK:就是状态码200,表示成功。Go 推荐用常量,代码可读性更高。
  • gin.H{...}:其实是 map[string]interface{} 的简写,用来快速构建 JSON 键值对,在实战中,我们通常会定义结构体(Struct)来代替 gin.H,但在快速测试时 gin.H 非常好用。

Example

不需要鉴权的登录接口,和获取用户信息接口

 package main
 ​
 import (
  "net/http"
 ​
  "github.com/gin-gonic/gin"
 )
 ​
 type User struct {
  ID   int    `json:"id"`
  Name string `json:"name"`
  Age  int    `json:"age"`
 }
 ​
 func main() {
  r := gin.Default()
  r.GET("/user/info", func(c *gin.Context) {
  user := User{
  ID:   1001,
  Name: "Ttdr",
  Age:  21,
  }
  c.JSON(http.StatusOK, gin.H{
  "code":    200,
  "message": "success",
  "data":    user,
  })
  })
  r.POST("/login", func(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
  "token":  "xyz123",
  "expire": 3600,
  })
  })
  r.Run("localhost:8080")
 }

获取URL的参数

我们在网站搜索的时候,我们往往会发现网址后面有一大串,such as http://taobao.com/search?keyword=手机&min_price=1000&max_price=2000,这种跟在?后面,用&链接的叫做查询字符串

我们点开某个商品的详细后发现,网址是http://taobao.com/item/10086,这个10086直接嵌入在网址路径里,表示id,也叫做路径参数

我们来看看后端如何获取到URL中的参数。

核心函数解析

1.c.Query("key")

  • 获取 URL 中 ? 后面的参数。
  • 在函数底层,是把 name=ttdr&page=1 解析成了一个 Map,直接取值就好
  • 返回的永远是 string 类型。如果需要数字(比如页码),后续需要用 strconv.Atoi() 转一下。

2.c.DefaultQuery("key", "default")

  • 为了防止用户偷懒不传参数,会设置默认值,比如分页接口,如果用户不传 page,我们默认给他展示第 1 页。

3.c.Param("key")/user/:key

  • 获取 URL 路径中的变量
  • 因为Gin是通过Radix Tree来存储路由,:id就像树上的通配符节点,匹配到这里的内容会被捕捉下来。

示例代码

 package main
 ​
 import (
  "net/http"
 ​
  "github.com/gin-gonic/gin"
 )
 ​
 func main() {
  r := gin.Default()
  /*获取Query参数*/
  r.GET("/search", func(c *gin.Context) {
  name := c.Query("name")
  page := c.DefaultQuery("page", "1")
  c.JSON(http.StatusOK, gin.H{
  "message":    "查询成功",
  "query_name": name,
  "query_page": page,
  })
  })
     /*获取 Path 参数*/
  r.GET("/user/:id", func(c *gin.Context) {
  userId := c.Param("id")
  c.JSON(http.StatusOK, gin.H{
  "msg":     "获取用户详情成功",
  "user_id": userId})
  })
  r.GET("/user/:id/post/:post_id", func(c *gin.Context) {
  userId := c.Param("id")
  postId := c.Param("post_id")
  c.JSON(http.StatusOK, gin.H{
  "user_id": userId,
  "post_id": postId,
  "info":    "用户 " + userId + " 的文章 " + postId,
  })
  })
  r.Run(":8080")
 }

参数绑定(Bind)

引入

前端丢来了一个长长的JSON字符串,我们这个时候要获得到这个结构体,我们需要读取Body的字节流,然后用JSON包解包,Gin简化了这个流程,可以把HTTP请求里的数据,直接映射并且赋值到你的结构体。

核心函数解析

1.c.ShouldBind(&ptr)

这是一个函数会检查 HTTP 请求头中的 Content-Type

  • 如果是 application/json,它内部自动调用 JSON 解析器。
  • 如果是 application/x-www-form-urlencoded,它自动调用表单解析器。

2.Struct Tags (结构体标签)

  • json:"name":将 JSON 中的 name 字段映射到结构体属性。
  • form:"name":将表单中的 name 字段映射到结构体属性。
  • binding:"required"校验规则。如果请求中缺少此字段,Gin 会直接报错,拒绝请求。

示例代码

 package main
 ​
 import (
  "net/http"
 ​
  "github.com/gin-gonic/gin"
 )
 ​
 type UserRegisterReq struct {
  Username string `json:"username"form:"username"binding:"required"`
  Password string `json:"password"form:"password"bingding:"required"`
  Age int `json:"age"form:"age"binding:"gte18"`
 }
 ​
 func main() {
  r := gin.Default()
  r.POST("/register", func(c *gin.Context) {
  var req UserRegisterReq
  err := c.ShouldBind(&req)
  if err != nil {
  c.JSON(http.StatusBadRequest, gin.H{
  "error": err,
  })
  return
  }
  c.JSON(http.StatusOK, gin.H{
  "status":   "success",
  "username": req.Username,
  "age":req.Age,
  })
  })
  r.Run(":8080")
 }
 ​

设计哲学

1.声明式校验 (Declarative Validation)

传统写法你需要写 if req.Age < 18 { return error },Gin 写法: binding:"gte=18" 你声明了“年龄必须大于等于18”。

2.ShouldBind 体现了 HTTP 协议中的内容协商思想。 后端不应该死板地规定“我只收 JSON”。Gin 的设计者认为,同一个接口,如果用户想发 Form 表单,或者想发 JSON,只要字段对得上,后端都应该能处理。这就是 ShouldBind 智能识别 Content-Type 的意义。

文件上传

引入

文件上传是项目中最常见的需求,文件的本质是二进制流,不能只放在JSO你里面传,需要HTTP一种特殊的编码方式:multipart.form-data。Gin吧这种复杂的工作封装成两个简单的函数。

核心函数解析

1.file,err := c.FormFile("key")

从请求中国读取文件头信息,参数为前端表单中的字段名,比如<input type="file" name="avatar">里面的avatar,返回值是一个对象,这个对象里包含了文件名,文件大小等数据,注意没有把整个文件所有内容读进内存

2.c.SaveUploadFile(file,dst)

将文件真正存到服务器硬盘上,file是上一步拿到的文件对象,dst是目标路径,

底层原理:

封装了 io.Copy,通过流式传输把数据从网络连接拷贝到硬盘,非常高效。

示例代码

 package main
 ​
 import (
  "net/http"
  "path/filepath"
 ​
  "github.com/gin-gonic/gin"
 )
 ​
 func main() {
  r := gin.Default()
  r.MaxMultipartMemory = 8 << 20
 ​
  r.POST("/upload/avatar", func(c *gin.Context) {
  file, err := c.FormFile("file") /*表单字段名为file*/
  if err != nil {
  c.JSON(http.StatusBadRequest, gin.H{"err:": "获取文件失败"})
  return
  }
  /*实际开发中为防止文件名冲突,会重命名文件比如用UUID*/
  dst := "./uploads/" + filepath.Base(file.Filename)
  if err := c.SaveUploadedFile(file, dst); err != nil {
  c.JSON(http.StatusInternalServerError, gin.H{"error": "保存失败"})
  return
  }
  c.JSON(http.StatusOK, gin.H{
  "status":   "success",
  "filename": file.Filename,
  "size":     file.Size,
  "path":     dst,
  })
  })
  r.Run(":8080")
 }

设计哲学

1.流式处理

Gin使用MultipartReader,调用SaveuploadedFile其实是建立了一条管道,数据从网线进来,经过CPU中转,直接流向硬盘,无论文件多大,内存里就只保留很小的一块缓冲区

2.filepath.Base(file.Filename)

为防止网站被攻击,通过拼接路径把文件覆盖到系统的敏感目录中,Base函数会去掉所有的目录符号,只保留文件名,更加安全。

补充:后缀校验

         ext := filepath.Ext(file.Filename)
  /*注意大小写转换,防止传的是.JPG*/
  ext = strings.ToLower(ext)
  if ext !=".jpg" && ext != ".png" {
  c.JSON(http.StatusBadRequest,gin.H{
  "error":"只允许上传.jpg和.png文件",
  })
  return
  }
暂无评论

发送评论 编辑评论


				
上一篇
下一篇