今天有个人问我有没有用 gin 写过前端页面, 问的我一脸懵逼. 我寻思不都前后端分离了, 还渲染什么.我翻了翻文档, 才发现原来 gin 还有模板渲染的功能…. 我震惊于自己从没有留意过这个功能;
今天就花点时间看一下 gin 模板渲染相关的知识, 算是给自己做一个补充.
基础渲染
- 创建一个新的目录 views,然后在其中创建一个新文件 index.html,内容如下:
1 2 3 4 5 6 7 8 9
| <!DOCTYPE html> <html> <head> <title>Gin Template Rendering</title> </head> <body> <h1>Hello, {{ .Name }}!</h1> </body> </html>
|
然后再 go 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package main
import ( "github.com/gin-gonic/gin" )
func main() { r := gin.Default() r.LoadHTMLGlob("views/*") r.GET("/", func(c *gin.Context) { c.HTML(200, "index.html", gin.H{ "Name": "Gin User", }) }) r.Run(":8080") }
|
在这段代码里, gin 将 name 的值 Gin User
传递给模板进行渲染.
运行上述 Go 代码,然后在浏览器中访问 http://localhost:8080/,您应该可以看到 “Hello, Gin User!”。
渲染 map 数据
1 2 3
| <h1>{{ .title }}</h1> <p>{{ .description }}</p>
|
golang 处理代码:
1 2 3 4 5 6 7 8 9
| r.GET("/map", func(c *gin.Context) {
c.HTML(200, "map.html", gin.H{ "title": "Hello from Map", "description": "This is a description using map.", })
})
|
渲染 List 数据
1 2 3 4 5 6
| <ul> {{ range . }} <li>{{ . }}</li> {{ end }} </ul>
|
golang 处理代码:
1 2 3 4 5
| r.GET("/list", func(c *gin.Context) { items := []string{"Apple", "Banana", "Cherry"} c.HTML(200, "list.html", items) })
|
渲染 Struct 数据
假设我们有一个表示书的结构体:
1 2 3 4 5
| type Book struct { Title string Author string }
|
1 2
| <h1>{{ .Title }}</h1> <p>By: {{ .Author }}</p>
|
golang 处理代码:
1 2 3 4 5
| r.GET("/struct", func(c *gin.Context) { book := Book{Title: "Golang Guide", Author: "Jane Doe"} c.HTML(200, "struct.html", book) })
|
渲染 List + Struct 数据
使用上面的 Book 结构体。
1 2 3 4 5 6
| <ul> {{ range . }} <li>{{ .Title }} by {{ .Author }}</li> {{ end }} </ul>
|
golang 处理代码:
1 2 3 4 5 6 7 8
| r.GET("/list_struct", func(c *gin.Context) { books := []Book{ {Title: "Golang Guide", Author: "Jane Doe"}, {Title: "Advanced Go", Author: "John Smith"}, } c.HTML(200, "list_struct.html", books) })
|
渲染 Map + List 数据
1 2 3 4 5 6 7
| <h1>{{ .title }}</h1> <ul> {{ range .items }} <li>{{ . }}</li> {{ end }} </ul>
|
golang 处理代码:
1 2 3 4 5 6 7 8
| r.GET("/map_list", func(c *gin.Context) { data := gin.H{ "title": "Fruits List", "items": []string{"Apple", "Banana", "Cherry"}, } c.HTML(200, "map_list.html", data) })
|
从 URL 查询字符串中获取参数:
1 2 3 4 5 6 7
| r.GET("/greet", func(c *gin.Context) { name := c.DefaultQuery("name", "Guest") c.HTML(http.StatusOK, "greet.html", gin.H{ "name": name, }) })
|
模板 greet.html:
1 2
| <h1>Hello, {{ .name }}!</h1>
|
访问: /greet?name=John 会显示 “Hello, John!”
从路径参数中获取参数:
1 2 3 4 5 6 7
| r.GET("/hello/:name", func(c *gin.Context) { name := c.Param("name") c.HTML(http.StatusOK, "hello.html", gin.H{ "name": name, }) })
|
模板 hello.html:
1 2
| <h1>Hello, {{ .name }}!</h1>
|
访问: /hello/John 会显示 “Hello, John!”
从请求体中获取参数(如 POST 请求):
首先,定义一个绑定到请求体的结构:
1 2 3 4
| type UserInfo struct { Name string `json:"name" form:"name"` }
|
然后:
1 2 3 4 5 6 7 8
| r.POST("/welcome", func(c *gin.Context) { var userInfo UserInfo c.Bind(&userInfo) c.HTML(http.StatusOK, "welcome.html", gin.H{ "name": userInfo.Name, }) })
|
模板 welcome.html:
1 2
| <h1>Welcome, {{ .name }}!</h1>
|
重定向
1 2 3
| r.GET("/redirect", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "https://52smile.vip") })
|
同步和异步
异步
对于路径 /long_async
的 GET 请求,服务器会异步处理。这是通过 go 关键字启动一个新的 Goroutine 来实现的。该 Goroutine 会休眠 3 秒钟,然后记录一个日志。注意,必须使用 c.Copy() 创建上下文的一个副本,因为原始的上下文在 Goroutine 外部可能已经被销毁。
1 2 3 4 5 6 7
| r.GET("/long_async", func(c *gin.Context) { copyContext := c.Copy() go func() { time.Sleep(3 * time.Second) log.Println("异步执行:" + copyContext.Request.URL.Path) }() })
|
同步
对于路径 /long_sync 的 GET 请求,服务器会同步地处理。这意味着它会直接在当前 Goroutine(线程)中执行,休眠 3 秒钟,然后记录一个日志。
1 2 3 4
| r.GET("/long_sync", func(c *gin.Context) { time.Sleep(3 * time.Second) log.Println("同步执行:" + c.Request.URL.Path) })
|
相关代码你可以在这里查看 模板渲染