|
|
# Gin框架的基本使用
## 下载与安装
> 更改环境变量 GOPATH
> > 方便将包下载到其他的盘符目录
> > 参考:[2024年go安装教程(Windows)包括配置GOPATH-CSDN博客](https://blog.csdn.net/weixin_63860405/article/details/139732041)
### 下载
```shell go get -u github.com/gin-gonic/gin ```

## 基础使用
### GET请求
```go
package main
import "github.com/gin-gonic/gin"
func main() { router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "Hello World!", }) }) router.Run() // 监听并在 0.0.0.0:8080 上启动服务 // 默认是在8080端口启动服务,如果8080端口被占用,则自动寻找可用端口启动服务,可以指定端口 } ```

### ANY请求
根据请求来选择(使用if else)
```go // Any 请求 router.Any("/any", func(c *gin.Context) { if c.Request.Method == "POST" { c.JSON(200, gin.H{ "message": "POST", }) } else if c.Request.Method == "GET" { c.JSON(200, gin.H{ "message": "GET", }) } else { c.JSON(200, gin.H{ "message": "Any", }) } }) ```
### POST请求
```go // POST 请求 router.POST("/post", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "POST", }) })
```

## 参数获取
### 获取query string 参数
格式:URL `?`后携带参数
例如:/user/searcher?username=xxx&address=xxx
```go // 获取参数,query string router.GET("/user/search", func(c *gin.Context) { username := c.DefaultQuery("username", "lihuibear") address := c.Query("address")
// 返回json数据 c.JSON(200, gin.H{ "username": username, "address": address, })
}) ```

### 获取 form参数
当前端请求的数据通过form表单提交时,例如向`/user/search`发送一个POST请求,获取请求数据的方式如下:
```go router.POST("/user/search_by_form", func(c *gin.Context) { // DefaultPostForm取不到值时会返回指定的默认值 //username := c.DefaultPostForm("username", "小王子") username := c.PostForm("username") address := c.PostForm("address") //输出json结果给调用方 c.JSON(http.StatusOK, gin.H{ "message": "ok", "username": username, "address": address, }) }) ```
### 获取JSON参数
当前端请求的数据通过JSON提交时,例如向`/json`发送一个JSON格式的POST请求,则获取请求参数的方式如下:
```go //获取JSON参数 router.POST("/json", func(c *gin.Context) { // 注意:下面为了举例子方便,暂时忽略了错误处理 b, _ := c.GetRawData() // 从c.Request.Body读取请求数据 // 定义map或结构体 var m map[string]interface{} // 反序列化 _ = json.Unmarshal(b, &m)
c.JSON(http.StatusOK, m) }) ```
### 获取path参数
请求的参数通过URL路径传递,例如:`/user/search/小王子/沙河`。 获取请求URL路径中的参数的方式如下:
```go // 获取path参数 router.GET("/user/search/:username/:address", func(c *gin.Context) { username := c.Param("username") address := c.Param("address") //输出json结果给调用方 c.JSON(http.StatusOK, gin.H{ "message": "ok", "username": username, "address": address, }) }) ```
### 参数绑定 ShouldBind()
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的`Content-Type`识别请求数据类型并利用反射机制自动提取请求中`QueryString`、`form表单`、`JSON`、`XML`等参数到结构体中。
```go // 参数绑定
// 绑定JSON的示例 ({"user": "q1mi", "password": "123456"}) router.POST("/login_by_JSON", func(c *gin.Context) { var login Login
if err := c.ShouldBind(&login); err == nil { fmt.Printf("login info:%#v\n", login) c.JSON(http.StatusOK, gin.H{ "user": login.User, "password": login.Password, }) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) // 绑定form表单示例 (user=q1mi&password=123456) router.POST("/login_by_form", func(c *gin.Context) { var login Login // ShouldBind()会根据请求的Content-Type自行选择绑定器 if err := c.ShouldBind(&login); err == nil { c.JSON(http.StatusOK, gin.H{ "user": login.User, "password": login.Password, }) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) // 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456) router.GET("/login_by_query", func(c *gin.Context) { var login Login // ShouldBind()会根据请求的Content-Type自行选择绑定器 if err := c.ShouldBind(&login); err == nil { c.JSON(http.StatusOK, gin.H{ "user": login.User, "password": login.Password, }) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) ```
`ShouldBind`会按照下面的顺序解析请求中的数据完成绑定:
1. 如果是 `GET` 请求,只使用 `Form` 绑定引擎(`query`)。 2. 如果是 `POST` 请求,首先检查 `content-type` 是否为 `JSON` 或 `XML`,然后再使用 `Form`(`form-data`)。
## Gin渲染
我们首先定义一个存放模板文件的`templates`文件夹,然后在其内部按照业务分别定义一个`posts`文件夹和一个`users`文件夹。 `posts/index.html`文件的内容如下:
```template <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>posts/index</title> </head> <body> {{.title}} </body> </html> {{end}} ```
`users/index.html`文件的内容如下:
```template {{define "users/index.html"}} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>users/index</title> </head> <body> {{.title}} </body> </html> {{end}} ```
Gin框架中使用`LoadHTMLGlob()`或者`LoadHTMLFiles()`方法进行HTML模板渲染。
```go func main() { r := gin.Default() r.LoadHTMLGlob("templates/**/*") //r.LoadHTMLFiles("templates/posts/index.html", "templates/users/index.html") r.GET("/posts/index", func(c *gin.Context) { c.HTML(http.StatusOK, "posts/index.html", gin.H{ "title": "posts/index", }) })
r.GET("users/index", func(c *gin.Context) { c.HTML(http.StatusOK, "users/index.html", gin.H{ "title": "users/index", }) })
r.Run(":8080") } ```
## 文件上传
### 单文件上传
```go // 加载模板 router.LoadHTMLFiles("./templates/upload.html") // 上传文件 router.GET("/upload", func(c *gin.Context) { c.HTML(http.StatusOK, "upload.html", nil) })
router.POST("/upload", func(c *gin.Context) { // 接收用户上传文件的post请求 f, err := c.FormFile("avatar") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "message": err.Error(), }) return } // 保存到指定目录 dst dst := "./upload/" + f.Filename c.SaveUploadedFile(f, dst)
// 返回响应 c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("'%s' uploaded!", f.Filename), }) }) ```
### 多文件上传
```go // 多文件上传
// 加载模板 router.LoadHTMLFiles("./templates/uploads.html") // 显示多文件上传页面 router.GET("/multi-upload", func(c *gin.Context) { c.HTML(http.StatusOK, "uploads.html", nil) }) // 多文件上传 router.POST("/multi-upload", func(c *gin.Context) { // Multipart form form, err := c.MultipartForm() if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "failed to parse multipart form"}) return }
files := form.File["file"]
for index, file := range files { log.Println(file.Filename) dst := fmt.Sprintf("./upload/%s_%d", file.Filename, index)
if err := c.SaveUploadedFile(file, dst); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } }
c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("%d files uploaded!", len(files)), }) }) ```
## 重定向
### http重定向
```go // http 重定向 router.GET("/abc", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/") }) ```
### 路由重定向
```go // 路由重定向 router.GET("/def", func(c *gin.Context) { c.Request.URL.Path = "/ping" router.HandleContext(c) })
```
## Gin路由
### 普通路由
```go r.GET("/index", func(c *gin.Context) {...}) r.GET("/login", func(c *gin.Context) {...}) r.POST("/login", func(c *gin.Context) {...}) ```
### any
```go r.Any("/test", func(c *gin.Context) {...}) ```
### 404页面
```go r.NoRoute(func(c *gin.Context) { c.HTML(http.StatusNotFound, "views/404.html", nil) }) ```
### 路由组
我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对`{}`包裹同组的路由,这只是为了看着清晰,你用不用`{}`包裹功能上没什么区别。
```go func main() { r := gin.Default() userGroup := r.Group("/user") { userGroup.GET("/index", func(c *gin.Context) {...}) userGroup.GET("/login", func(c *gin.Context) {...}) userGroup.POST("/login", func(c *gin.Context) {...})
} shopGroup := r.Group("/shop") { shopGroup.GET("/index", func(c *gin.Context) {...}) shopGroup.GET("/cart", func(c *gin.Context) {...}) shopGroup.POST("/checkout", func(c *gin.Context) {...}) } r.Run() } ```
路由组也是支持嵌套的,例如:
```go shopGroup := r.Group("/shop") { shopGroup.GET("/index", func(c *gin.Context) {...}) shopGroup.GET("/cart", func(c *gin.Context) {...}) shopGroup.POST("/checkout", func(c *gin.Context) {...}) // 嵌套路由组 xx := shopGroup.Group("xx") xx.GET("/oo", func(c *gin.Context) {...}) } ```
通常我们将路由分组用在划分业务逻辑或划分API版本时。
### 路由原理
Gin框架中的路由使用的是[httprouter](https://github.com/julienschmidt/httprouter)这个库。
其基本原理就是构造一个路由地址的前缀树。
|