参考资料
- https://www.liwenzhou.com/posts/Go/go_template/#autoid-1-2-4
- https://www.bilibili.com/video/BV1gJ411p7xC
概述
为什么需要go模板?在一些前后端不分离的Web架构中,我们通常需要在后端将一些数据渲染到HTML文档中,从而实现动态的网页(网页的布局和样式大致一样,但展示的内容并不一样)效果。对模板的渲染操作可以简单理解为文本替换操作,即使用相应的数据去替换HTML文档中事先准备好的标记。
go template engine的作用机制为?
- 模板文件通常为一个HTML界面
- 使用 {{}} 标识需要传入的数据
- 传给模板这样的数据就可以通过点号(.)来访问
- 除 {{}} 包裹的内容外,其他内容均不做修改原样输出
使用步骤
如何使用go模板引擎?
- 创建模板文件;
解析模板,得到模板对象;
func (t *Template) Parse(src string) (*Template, error)
func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)
func template.New("template_name").Parse(src string) (*Template, error)
模板渲染(使用数据填充模板); ```go func (t *Template) Execute(wr io.Writer, data interface{}) error
// 对应上面的template_name func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
简单示例:<br />hello.html
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello</title>
</head>
<body>
<p>Hello {{.}}</p>
</body>
</html>
main.go
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":8888", nil)
if err != nil {
fmt.Println("HTTP server failed: %v", err)
}
}
func sayHello(w http.ResponseWriter, r *http.Request) {
// 解析模板,得到模板对象
tmpl, err := template.ParseFiles("test/hello.html")
if err != nil {
fmt.Println("create template failed, err: %v", err)
return
}
// 将数据写入模板,结果返回至http.ResponseWriter
tmpl.Execute(w, "富贵🐷")
}
template语法
传入对象
传递结构体:
主要是理解{{.}}中间的”.”表示通过execute()传递进来的对象。
type User struct {
Name string
Age int
Gender string
}
func handler(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("template/hello.html")
if err != nil {
fmt.Printf("error when parse template: %v", err)
}
u := User{"富贵🐷", 1, "小公猫"}
t.Execute(w, u)
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":9090", nil)
}
模板文件:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello</title>
</head>
<body>
<p>名字: {{.Name}}</p>
<p>年龄: {{.Age}}岁</p>
<p>是一个: {{.Gender}}</p>
</body>
</html>
传递map:
func handler(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("template/hello.html")
if err != nil {
fmt.Printf("error when parse template: %v", err)
}
m := map[string]interface{}{
"Name": "富贵🐷",
"Gender": "小公猫",
"Age": 1,
}
t.Execute(w, m)
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":9090", nil)
}
传入多个对象:
type User struct {
Name string
Age int
Gender string
}
func handler(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("template/hello.html")
if err != nil {
fmt.Printf("error when parse template: %v", err)
}
m := map[string]interface{}{
"name": "富贵🐷",
"gender": "小公猫",
"age": 1,
}
u := User{
Name: "富贵🐷",
Gender: "小公猫",
Age: 1,
}
t.Execute(w, map[string]interface{}{
"m": m,
"u": u,
})
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":9090", nil)
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello</title>
</head>
<body>
<p>名字: {{.u.Name}}</p>
<p>年龄: {{.u.Age}}岁</p>
<p>是一只: {{.u.Gender}}</p>
<p>名字: {{.m.name}}</p>
<p>年龄: {{.m.age}}岁</p>
<p>是一只: {{.m.gender}}</p>
</body>
</html>
条件判断
什么是pipeline?
pipeline表示数据传递的操作。如{{.}}
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
变量
注意前面带$号
$obj := {{.}}
range
hobbylist := []string{
"football",
"PingPong",
"Guitar",
}
t.Execute(w, map[string]interface{}{
"hobby": hobbylist,
})
{{ range $idx, $hobby := .hobby }}
<p>idx:{{$idx}} - {{$hobby}}</p>
{{ end }}
with
with声明的对象在end之前可以用.代替
{{with .u}}
<p>名字: {{.Name}}</p>
<p>年龄: {{.Age}}岁</p>
<p>是一只: {{.Gender}}</p>
{{end}}
比较函数
eq 如果arg1 == arg2则返回真
ne 如果arg1 != arg2则返回真
lt 如果arg1 < arg2则返回真
le 如果arg1 <= arg2则返回真
gt 如果arg1 > arg2则返回真
ge 如果arg1 >= arg2则返回真
自定义函数
func f1(w http.ResponseWriter, r *http.Request) {
// 自定义函数
kua := func(name string) string {
return name + " is so cute"
}
// 解析模板,在模板map中注册自定义函数
t, err := template.New("hello.html").
Funcs(template.FuncMap{"k": kua}).ParseFiles("template/hello.html")
if err != nil {
fmt.Printf("error when parse template: %v", err)
}
name := "富贵🐷"
// 渲染模板
t.Execute(w, name)
}
func main() {
http.HandleFunc("/hello", f1)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Printf("HTTP server start failed, err:%v\n", err)
}
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello</title>
</head>
<body>
{{k .}}
</body>
</html>
模板嵌套
ul.html
<ul>
<li>注释</li>
<li>日志</li>
<li>测试</li>
</ul>
hello.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello</title>
</head>
<body>
{{.}}
<hr>
{{template "ul.html"}}
<hr>
{{template "ol.html"}}
</body>
</html>
<!-- 自定义一个模板 -->
{{define "ol.html"}}
<ol>
<li>吃饭</li>
<li>😴</li>
<li>打豆豆</li>
</ol>
{{end}}
main.go
func f1(w http.ResponseWriter, r *http.Request) {
// 解析模板,在模板map中注册自定义函数
t, err := template.ParseFiles("template/hello.html", "template/ul.html")
if err != nil {
fmt.Printf("error when parse template: %v", err)
}
name := "富贵🐷"
// 渲染模板
t.Execute(w, name)
}
func main() {
http.HandleFunc("/hello", f1)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Printf("HTTP server start failed, err:%v\n", err)
}
}
注意:在解析模板时,主模板(返回给客户端的页面)一定要放在第一个
页面返回结果:
模板继承
根模板:
注意block中间的{{.}},说明数据先被传入根模板,子模板的数据则来自于根模板。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>模板继承</title>
<style>
* {
margin: 0;
}
.nav {
height: 50px;
width: 100%;
position: fixed;
top: 0;
background-color: burlywood;
}
.main {
margin-top: 50px;
}
.menu {
width: 20%;
height: 100%;
position:fixed;
left: 0;
background-color: cornflowerblue;
}
.center {
text-align: center;
}
</style>
</head>
<body>
<div class="nav"></div>
<div class="main">
<div class="menu"></div>
<div class="content center">
{{block "content" .}}{{end}}
</div>
</div>
</body>
</html>
index.html(该模板继承了根模板)
<!-- 继承base模板 -->
{{template "base.html" .}} <!-- .代表承接根模板数据 -->
<!-- 替换模板的block部分 -->
{{define "content"}}
<h1>this is index page</h1>
<p>HELLO {{.}}</p>
{{end}}
mian.go
func index(w http.ResponseWriter, r *http.Request) {
// 解析模板,在模板map中注册自定义函数
t, err := template.ParseFiles("template/base.html", "template/index.html")
if err != nil {
fmt.Printf("error when parse template: %v\n", err)
}
name := "富贵🐷"
// 渲染指定模板
t.ExecuteTemplate(w, "index.html", name)
}
func main() {
http.HandleFunc("/index", index)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Printf("HTTP server start failed, err:%v\n", err)
}
}
如果我们的模板名称冲突了,例如不同业务线下都定义了一个index.tmpl模板,我们可以通过下面两种方法来解决。
- 在模板文件开头使用{{define 模板名}}语句显式的为模板命名。
- 可以把模板文件存放在templates文件夹下面的不同目录中,然后使用template.ParseGlob(“templates/*/.tmpl”)解析模板。
标识符冲突解决
Go标准库的模板引擎使用的花括号{{和}}作为标识,而许多前端框架(如Vue和 AngularJS)也使用{{和}}作为标识符,所以当我们同时使用Go语言模板引擎和以上前端框架时就会出现冲突,这个时候我们需要修改标识符,修改前端的或者修改Go语言的。这里演示如何修改Go语言模板引擎默认的标识符:
template.New("test").Delims("{[", "]}").ParseFiles("./t.tmpl")
text/template与html/template
html/template针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。
如果用户传入一个JS语句替换{{.}},那么页面可能会被改变。但是html/template会在页面上显示出转义后的JS内容。