gin框架中默认的HTML模板渲染使用 LoadHTMLGlob() 或者 LoadHTMLFiles() , 这个地方如果是使用的LoadHTMLGlob() 这个方法的话是有坑的,即当你的模板文件放在不同的文件夹中时,使用这个方式加载会将文件夹也作为文件加载进去,从而直接在模板渲染的时候抛异常!!
假设在我们的项目templates文件夹中有如下结构的模板文件:
├── article
│ ├── detail.html
│ └── index.html
├── footer.html
├── header.html
├── index.html
└── search
├── detail.html
└── index.html
r := gin.Default()
LoadHTMLGlob模板加载匹配模式
r.LoadHTMLGlob("templates/*.html") 如果是这种模式就只能加载顶层的3个html文件,子目录中的文件无法加载
r.LoadHTMLGlob("templates/**/*.html") 这种方式就只能加载子目录article, search中一共4个文件
r.LoadHTMLGlob("template/**/*") 这种模式 只能加载子目录中的所有文件或者文件夹,顶层的3个模板无法加载
r.LoadHTMLGlob("template/**") 这种模式 可以加载所有的文件和目录(坑,模板编译时直接报异常, 不能使用这种方式!)
上面的4种方式加载模板对于我们上面的模板结构都不可用,直接抛弃!
上面的方式只能适用于没有子目录的模板加载
filepath.WalkDir + LoadHTMLFiles() 的方式加载模板
对于我们上面的模板结构只能采用LoadHTMLFiles()方式加载了, 使用这个方式加载,我们需要先自己加载我们需要的模板文件, 这个就需要使用filepath.WalkDir 这个目录遍历大杀器出场了。废话补多少直接删代码:
// 模板文件加载
var tplFiles []string
filepath.WalkDir("templates", func(path string, d fs.DirEntry, err error) error {
if !d.IsDir() && strings.HasSuffix(d.Name(), ".html") {
// 非目录,且是.html结尾的文件加入到模板文件列表
tplFiles = append(tplFiles, path)
}
return nil
})
r.LoadHTMLFiles(tplFiles...) // 加载html模板
在gin控制器中通过.HTML方法使用模板
这个非常简单直接用HTML方法即可。 需要注意的是这里的模板名称是很讲究的,我们来看一下这个HTML方法的原型: func (c *gin.Context) HTML(code int, name string, obj any)
这里第一个参数是http的状态码一般都是200即可, 第二个参数name 就是我们要渲染的模板的名称,这里的名称默认是你加载的文件的不区分目录的文件名, 由于默认不区分目录,所以如果你有多个同名的文件,则必须在对应的文件中使用 define对文件名进行定义,如: {{ define "index.html"}} 你的模板内容 {{end}} 。 如果你加载的文件名每个都是唯一的,则不需要定义也可以直接使用。
r.GET("/index", func(c *gin.Context) {
// 注意这里的第二个参数 模板名是很有讲究的
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "Main website",
})
})
r.Run(":8080")
html模板文件示例和模板包含命名定义使用示例
index.html 示例:
注意,由于index.html文件名称有重复,所以这里我们必须使用define对文件名进行定义,否则index.html加载的就可能不是你期望的文件!!!
另外这里我们使用内置函数 template 对另外2个公共的模板文件进行加载。 {{template "xxx.html" .}}
{{define "index.html"}}
<!DOCTYPE html>
<html>
<head>
{{template "header.html" .}}
</head>
<body>
<div class="container-fluid">
<H1>Hello Gin HTML</H1>
<!--- footer --->
{{template "footer.html" .}}
</div>
</body>
</html>
{{end}}
head.html
这个head.html文件名是唯一的,所以不需要是哟共define定义也可使用, 注意这里的标题 {{.title}} 这个直接使用了 c.HTML方法的第三个参数中传递的动态变量
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<title>{{.title}}</title>
footer.html
<!--- footer --->
<footer style="height: 100px; background: #1b1c1d">
<div style="text-align: center; font-size: 14px; padding-top: 50px">
By
<a href="http://dev.tekin.cn" target="_blank"
>{{.title}}</a>
</div>
</footer>
article/index.html文件内容
注意这里的define定义的文件名 {{ define "article/index.html"}} 我们一般以当前文件所在的模板文件夹下的相对路径来命名。 在c.HTML里面使用的时候我们就使用 c.HTML(200, "article/index.html", nil) 即可。
{{ define "article/index.html"}}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Article index</title>
<link rel="stylesheet" href="">
</head>
<body>
<h1>Hello my article</h1>
</body>
</html>
{{end}}
自定义模板渲染器
你可以使用自定义的 html 模板渲染
import "html/template"
func main() {
router := gin.Default()
html := template.Must(template.ParseFiles("file1", "file2"))
router.SetHTMLTemplate(html)
router.Run(":8080")
}
自定义分隔符
r.Delims("{[{", "}]}") // 自定义模板分隔符 这个一般情况下我们保持默认即可
自定义模板功能
这个就是使用.SetFuncMap加载我们自定义的函数进去就可以。 推荐在c.HTML里面绑定对象的方式调用对象方法更方便,调用方式为 将函数名换为 对象名.方法名
import (
"fmt"
"html/template"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func formatAsDate(t time.Time) string {
year, month, day := t.Date()
return fmt.Sprintf("%d/d/d", year, month, day)
}
func main() {
router := gin.Default()
router.Delims("{[{", "}]}")
router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate,
})
router.LoadHTMLFiles("templates/index.html")
router.GET("/raw", func(c *gin.Context) {
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
})
})
router.Run(":8080")
}
在模板中使用我们自定义的函数:
Date: {[{.now | formatAsDate}]} 这个是使用管道符 | 方式,
也可以这样使用 Date: {[{formatAsDate .now }]} 如果是多个参数用空格分开即可。
附录 go内置的默认模板函数
func builtins() FuncMap {
return FuncMap{
"and": and,
"call": call,
"html": HTMLEscaper,
"index": index,
"slice": slice,
"js": JSEscaper,
"len": length,
"not": not,
"or": or,
"print": fmt.Sprint,
"printf": fmt.Sprintf,
"println": fmt.Sprintln,
"urlquery": URLQueryEscaper,
// Comparisons
"eq": eq, // ==
"ge": ge, // >=
"gt": gt, // >
"le": le, // <=
"lt": lt, // <
"ne": ne, // !=
}
}
gin框架HTML渲染使用总结:
1. gin框架中渲染模板必须要先加载,如果模板文件中没有子目录可以使用 LoadHTMLGlob() ,如果有子目录推荐使用 filepath.WalkDir + LoadHTMLFiles() 的方式加载模板;
2. 模板名称默认为加载的文件的忽略目录的文件名, 如果有多个同名文件,则默认使用功能的是第一个加载的模板文件;
3. 在模板中包含其他模板可以使用template函数进行加载包含,即 {{template "xxx.html" .}}
4. gin中模板渲染使用c.HTML方法,这个方法有3个参数,第一个是http状态码,第二个是模板名称,第三个是要传递给模板的变量数据, 这个我们一般使用map数据结构
5. 自定义模板函数实际上就是将自己定义的函数使用 r.SetFuncMap(template.FuncMap{ "模板中使用的函数名": 函数名,})
6. 一般情况下我们可以通过在c.HTML的第三个参数里面绑定一个对象来代替自定义函数, 直接在模板中调用对象的方法更加方便和简洁,不需要另外绑定函数即可使用。