今天开始学习Go Web,目前主流的框架有gingo-zeroecho等。我还是随主流,选择gin

今天要学习的知识点是:

  • 启动Gin服务
  • 定义Get / POST / PUT / DELETE / 路由
  • 获取路径参数
  • 获取query参数
  • 返回JSON响应
  • 路由分组

创建一个gin项目

初始化项目

1
2
3
4
5
6
# 创建项目目录
mkdir goweb
cd goweb

# 初始化go mode
go mod init dev.net.cn/goweb

执行完成后,会生成一个go.mod文件,用来管理项目的依赖和版本。

配置代理

为了更快的下载Go的模块,这里推荐大陆用户配置代理

1
2
3
4
5
# 如果是 Linux / macOS
export GOPROXY=https://goproxy.cn,direct

# 如果是 Windows (PowerShell)
$env:GOPROXY="https://goproxy.cn,direct"

下载gin依赖

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

编写主程序代码

在项目根目录创建main.go,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"
)

func main() {
// 创建一个默认的Gin路由引擎(内置了Logger和Recovery)
r := gin.Default()
// 创建一个新的Gin路由引擎(不带任何中间件)
// r := gin.New()

// 绑定路由及操作 @Mapping("/hello")
r.GET("/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "hello go web",
"status": "ok",
})
})
// 指定端口,默认就是8080
err := r.Run(":8080")
if err != nil {
fmt.Println("start error...")
}
}

测试

1
curl http://localhost:8080/hello

返回

1
{"message":"hello go web","status":"ok"}

其中c.JSON(http.StatusOK, gin.H{...})表示返回JSON响应,gin.H本质上是:

1
map[string]any

Gin中的gin.Default()gin.New()的主要区别:gin.Default()默认包含了LoggerRecovery中间件;gin.New()创建一个不带默认中间件的engine,需要自己手动添加中间件。暂时先用Default

路径参数:c.Param

类似于:@PathVariable("id"),假设要获取一个路径参数,可以使用如下方式:

1
2
3
4
5
6
7
r.GET("/users/:id", func(c *gin.Context) {
// 获取路径参数 id
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"id": id,
})
})

访问

1
curl http://localhost:8080/users/666

响应:

1
{"id":"666"}

默认拿到的路径参数是string类型。如需其他类型需要自行转换。

Query参数:c.Query/c.DefaultQuery

类似于:@ReuqestParam("xxx")如果是Query参数,那么就需要如下方式:

1
2
3
4
5
6
7
8
9
10
11
12
r.GET("/users", func(c *gin.Context) {
// 如果参数不存在,返回默认值
page := c.DefaultQuery("page", "1")
pageSize := c.DefaultQuery("page_size", "10")
// 参数不存在 返回空字符串
keyword := c.Query("keyword")
c.JSON(http.StatusOK, gin.H{
"page": page,
"page_size": pageSize,
"keyword": keyword,
})
})
  • DefaultQuery : 如果不存在,返回默认值
  • Query: 如果不存在,返回空字符串。

路径参数与Query参数的使用区别:

  • 路径参数通常表示资源标识,例如/users/:id
  • Query参数通常表示过略、分页、排序条件。

POST/PUT/DELETE请求

上面的例子都是GET请求,下面介绍其他几个请求方式。本质上和Spring的没啥区别。

POST

对于POST,首先需要参数绑定,计划明天学习,先来实践一下:

1
2
3
4
5
6
7
8
9
10
type User struct {
// binding: "required 表示该字段必填,否则报错 对应Java @NotNull"
Name string `json:"name" binding:"required"`
// 限制年龄在0-130
Age int `json:"age" binding:"gte=0,lte=130"`
}

type UserURI struct {
ID string `uri:"id" binding:"required"`
}

其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
r.POST("/users", func(c *gin.Context) {
var newUser User
//c.ShouldBindJSON(&newUser); 相当于 @RequestBody
if err := c.ShouldBindJSON(&newUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
}
c.JSON(http.StatusOK, gin.H{
"status": "user created",
"data": newUser,
})
})

c.ShouldBindJSON(&newUser)相当于Spring中的@ReuqestBody,将请求体中的json绑定到User中。

1
2
3
4
5
POST http://localhost:8080/users
{
"name": "tom",
"age": 22
}

PUT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
r.PUT("/users/:id", func(c *gin.Context) {
var uriData UserURI
var updateData User

if err := c.ShouldBindUri(&uriData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
if err := c.ShouldBindJSON(&updateData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "user updated",
"userId": uriData.ID,
"newData": updateData,
})
})

代码综合GET/POST

1
2
3
4
5
PUT http://localhost:8080/users/123
{
"name":"jerry",
"age": 18
}

Delete

1
2
3
4
5
6
7
8
9
10
11
12
13
r.DELETE("/users/:id", func(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "id is required",
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "user deleted",
"id": id,
})
})

请求报文

1
DELETE http://localhost:8080/users/123

路由分组:RouterGroup

可以将某一类接口进行分组。相当于Java加在Controller类上的@Mapping("/xxx")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
func main() {
// 创建一个默认的Gin路由引擎(内置了Logger和Recovery)
r := gin.Default()
api := r.Group("/api/v1")
{
api.GET("/users", listUser)
api.GET("/users/:id", getUser)
api.POST("/users", createUser)
}

err := r.Run(":8080")
if err != nil {
fmt.Println("start error...")
}
}

func listUser(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"items": []gin.H{
{"id": 1, "name": "tom"},
{"id": 2, "name": "jack"},
},
})
}

func getUser(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"id": id,
"name": "tom",
})
}

func createUser(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"id": 1,
"name": "tom",
})
}

请求:

1
GET http://localhost:8080/api/v1/users

响应:

1
2
3
4
5
6
7
8
9
10
11
12
{
"items": [
{
"id": 1,
"name": "tom"
},
{
"id": 2,
"name": "jack"
}
]
}

路由分组适合做API版本模块分区权限分区