今天开始学习Go Web,目前主流的框架有gin、go-zero、echo等。我还是随主流,选择gin。
今天要学习的知识点是:
启动Gin服务
定义Get / POST / PUT / DELETE / 路由
获取路径参数
获取query参数
返回JSON响应
路由分组
创建一个gin项目 初始化项目 1 2 3 4 5 6 mkdir gowebcd gowebgo mod init dev.net.cn/goweb
执行完成后,会生成一个go.mod文件,用来管理项目的依赖和版本。
配置代理 为了更快的下载Go的模块,这里推荐大陆用户配置代理
1 2 3 4 5 export GOPROXY=https://goproxy.cn,direct$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 mainimport ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func main () { r := gin.Default() r.GET("/hello" , func (c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message" : "hello go web" , "status" : "ok" , }) }) 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本质上是:
Gin中的gin.Default()和gin.New()的主要区别:gin.Default()默认包含了Logger和Recovery中间件;gin.New()创建一个不带默认中间件的engine,需要自己手动添加中间件。暂时先用Default。
路径参数:c.Param 类似于:@PathVariable("id"),假设要获取一个路径参数,可以使用如下方式:
1 2 3 4 5 6 7 r.GET("/users/:id" , func (c *gin.Context) { id := c.Param("id" ) c.JSON(http.StatusOK, gin.H{ "id" : id, }) })
访问
1 curl http://localhost:8080/users/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 { Name string `json:"name" binding:"required"` 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 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: { "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: { "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 () { 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版本、模块分区、权限分区。