今天有个人问我有没有用 gin 写过前端页面, 问的我一脸懵逼. 我寻思不都前后端分离了, 还渲染什么.我翻了翻文档, 才发现原来 gin 还有模板渲染的功能…. 我震惊于自己从没有留意过这个功能;
今天就花点时间看一下 gin 模板渲染相关的知识, 算是给自己做一个补充.

基础渲染

  1. 创建一个新的目录 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") // 默认值为 "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)
})

相关代码你可以在这里查看 模板渲染