参考资料

  1. https://www.liwenzhou.com/posts/Go/go_template/#autoid-1-2-4
  2. https://www.bilibili.com/video/BV1gJ411p7xC

    概述

    为什么需要go模板?

    在一些前后端不分离的Web架构中,我们通常需要在后端将一些数据渲染到HTML文档中,从而实现动态的网页(网页的布局和样式大致一样,但展示的内容并不一样)效果。对模板的渲染操作可以简单理解为文本替换操作,即使用相应的数据去替换HTML文档中事先准备好的标记。

go template engine的作用机制为?

  • 模板文件通常为一个HTML界面
  • 使用 {{}} 标识需要传入的数据
  • 传给模板这样的数据就可以通过点号(.)来访问
  • 除 {{}} 包裹的内容外,其他内容均不做修改原样输出

使用步骤

如何使用go模板引擎?

  1. 创建模板文件;
  2. 解析模板,得到模板对象;

    1. func (t *Template) Parse(src string) (*Template, error)
    2. func ParseFiles(filenames ...string) (*Template, error)
    3. func ParseGlob(pattern string) (*Template, error)
    4. func template.New("template_name").Parse(src string) (*Template, error)
  3. 模板渲染(使用数据填充模板); ```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

  1. 简单示例:<br />hello.html
  2. ```html
  3. <!DOCTYPE html>
  4. <html lang="zh-CN">
  5. <head>
  6. <title>Hello</title>
  7. </head>
  8. <body>
  9. <p>Hello {{.}}</p>
  10. </body>
  11. </html>

main.go

  1. func main() {
  2. http.HandleFunc("/", sayHello)
  3. err := http.ListenAndServe(":8888", nil)
  4. if err != nil {
  5. fmt.Println("HTTP server failed: %v", err)
  6. }
  7. }
  8. func sayHello(w http.ResponseWriter, r *http.Request) {
  9. // 解析模板,得到模板对象
  10. tmpl, err := template.ParseFiles("test/hello.html")
  11. if err != nil {
  12. fmt.Println("create template failed, err: %v", err)
  13. return
  14. }
  15. // 将数据写入模板,结果返回至http.ResponseWriter
  16. tmpl.Execute(w, "富贵🐷")
  17. }

template语法

传入对象

传递结构体:
主要是理解{{.}}中间的”.”表示通过execute()传递进来的对象。

  1. type User struct {
  2. Name string
  3. Age int
  4. Gender string
  5. }
  6. func handler(w http.ResponseWriter, r *http.Request) {
  7. t, err := template.ParseFiles("template/hello.html")
  8. if err != nil {
  9. fmt.Printf("error when parse template: %v", err)
  10. }
  11. u := User{"富贵🐷", 1, "小公猫"}
  12. t.Execute(w, u)
  13. }
  14. func main() {
  15. http.HandleFunc("/hello", handler)
  16. http.ListenAndServe(":9090", nil)
  17. }

模板文件:

  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <title>Hello</title>
  5. </head>
  6. <body>
  7. <p>名字: {{.Name}}</p>
  8. <p>年龄: {{.Age}}岁</p>
  9. <p>是一个: {{.Gender}}</p>
  10. </body>
  11. </html>

传递map:

  1. func handler(w http.ResponseWriter, r *http.Request) {
  2. t, err := template.ParseFiles("template/hello.html")
  3. if err != nil {
  4. fmt.Printf("error when parse template: %v", err)
  5. }
  6. m := map[string]interface{}{
  7. "Name": "富贵🐷",
  8. "Gender": "小公猫",
  9. "Age": 1,
  10. }
  11. t.Execute(w, m)
  12. }
  13. func main() {
  14. http.HandleFunc("/hello", handler)
  15. http.ListenAndServe(":9090", nil)
  16. }

传入多个对象:

  1. type User struct {
  2. Name string
  3. Age int
  4. Gender string
  5. }
  6. func handler(w http.ResponseWriter, r *http.Request) {
  7. t, err := template.ParseFiles("template/hello.html")
  8. if err != nil {
  9. fmt.Printf("error when parse template: %v", err)
  10. }
  11. m := map[string]interface{}{
  12. "name": "富贵🐷",
  13. "gender": "小公猫",
  14. "age": 1,
  15. }
  16. u := User{
  17. Name: "富贵🐷",
  18. Gender: "小公猫",
  19. Age: 1,
  20. }
  21. t.Execute(w, map[string]interface{}{
  22. "m": m,
  23. "u": u,
  24. })
  25. }
  26. func main() {
  27. http.HandleFunc("/hello", handler)
  28. http.ListenAndServe(":9090", nil)
  29. }
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <title>Hello</title>
  5. </head>
  6. <body>
  7. <p>名字: {{.u.Name}}</p>
  8. <p>年龄: {{.u.Age}}岁</p>
  9. <p>是一只: {{.u.Gender}}</p>
  10. <p>名字: {{.m.name}}</p>
  11. <p>年龄: {{.m.age}}岁</p>
  12. <p>是一只: {{.m.gender}}</p>
  13. </body>
  14. </html>

条件判断

什么是pipeline?
pipeline表示数据传递的操作。如{{.}}

  1. {{if pipeline}} T1 {{end}}
  2. {{if pipeline}} T1 {{else}} T0 {{end}}
  3. {{if pipeline}} T1 {{else if pipeline}} T0 {{end}}

变量

注意前面带$号

  1. $obj := {{.}}

range

  1. hobbylist := []string{
  2. "football",
  3. "PingPong",
  4. "Guitar",
  5. }
  6. t.Execute(w, map[string]interface{}{
  7. "hobby": hobbylist,
  8. })
  1. {{ range $idx, $hobby := .hobby }}
  2. <p>idx:{{$idx}} - {{$hobby}}</p>
  3. {{ end }}

with

with声明的对象在end之前可以用.代替

  1. {{with .u}}
  2. <p>名字: {{.Name}}</p>
  3. <p>年龄: {{.Age}}岁</p>
  4. <p>是一只: {{.Gender}}</p>
  5. {{end}}

比较函数

  1. eq 如果arg1 == arg2则返回真
  2. ne 如果arg1 != arg2则返回真
  3. lt 如果arg1 < arg2则返回真
  4. le 如果arg1 <= arg2则返回真
  5. gt 如果arg1 > arg2则返回真
  6. ge 如果arg1 >= arg2则返回真

自定义函数

  1. func f1(w http.ResponseWriter, r *http.Request) {
  2. // 自定义函数
  3. kua := func(name string) string {
  4. return name + " is so cute"
  5. }
  6. // 解析模板,在模板map中注册自定义函数
  7. t, err := template.New("hello.html").
  8. Funcs(template.FuncMap{"k": kua}).ParseFiles("template/hello.html")
  9. if err != nil {
  10. fmt.Printf("error when parse template: %v", err)
  11. }
  12. name := "富贵🐷"
  13. // 渲染模板
  14. t.Execute(w, name)
  15. }
  16. func main() {
  17. http.HandleFunc("/hello", f1)
  18. err := http.ListenAndServe(":9090", nil)
  19. if err != nil {
  20. fmt.Printf("HTTP server start failed, err:%v\n", err)
  21. }
  22. }
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <title>Hello</title>
  5. </head>
  6. <body>
  7. {{k .}}
  8. </body>
  9. </html>

模板嵌套

ul.html

  1. <ul>
  2. <li>注释</li>
  3. <li>日志</li>
  4. <li>测试</li>
  5. </ul>

hello.html

  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <title>Hello</title>
  5. </head>
  6. <body>
  7. {{.}}
  8. <hr>
  9. {{template "ul.html"}}
  10. <hr>
  11. {{template "ol.html"}}
  12. </body>
  13. </html>
  14. <!-- 自定义一个模板 -->
  15. {{define "ol.html"}}
  16. <ol>
  17. <li>吃饭</li>
  18. <li>😴</li>
  19. <li>打豆豆</li>
  20. </ol>
  21. {{end}}

main.go

  1. func f1(w http.ResponseWriter, r *http.Request) {
  2. // 解析模板,在模板map中注册自定义函数
  3. t, err := template.ParseFiles("template/hello.html", "template/ul.html")
  4. if err != nil {
  5. fmt.Printf("error when parse template: %v", err)
  6. }
  7. name := "富贵🐷"
  8. // 渲染模板
  9. t.Execute(w, name)
  10. }
  11. func main() {
  12. http.HandleFunc("/hello", f1)
  13. err := http.ListenAndServe(":9090", nil)
  14. if err != nil {
  15. fmt.Printf("HTTP server start failed, err:%v\n", err)
  16. }
  17. }

注意:在解析模板时,主模板(返回给客户端的页面)一定要放在第一个

页面返回结果:
image.png

模板继承

根模板:
注意block中间的{{.}},说明数据先被传入根模板,子模板的数据则来自于根模板。

  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <title>模板继承</title>
  5. <style>
  6. * {
  7. margin: 0;
  8. }
  9. .nav {
  10. height: 50px;
  11. width: 100%;
  12. position: fixed;
  13. top: 0;
  14. background-color: burlywood;
  15. }
  16. .main {
  17. margin-top: 50px;
  18. }
  19. .menu {
  20. width: 20%;
  21. height: 100%;
  22. position:fixed;
  23. left: 0;
  24. background-color: cornflowerblue;
  25. }
  26. .center {
  27. text-align: center;
  28. }
  29. </style>
  30. </head>
  31. <body>
  32. <div class="nav"></div>
  33. <div class="main">
  34. <div class="menu"></div>
  35. <div class="content center">
  36. {{block "content" .}}{{end}}
  37. </div>
  38. </div>
  39. </body>
  40. </html>

index.html(该模板继承了根模板)

  1. <!-- 继承base模板 -->
  2. {{template "base.html" .}} <!-- .代表承接根模板数据 -->
  3. <!-- 替换模板的block部分 -->
  4. {{define "content"}}
  5. <h1>this is index page</h1>
  6. <p>HELLO {{.}}</p>
  7. {{end}}

mian.go

  1. func index(w http.ResponseWriter, r *http.Request) {
  2. // 解析模板,在模板map中注册自定义函数
  3. t, err := template.ParseFiles("template/base.html", "template/index.html")
  4. if err != nil {
  5. fmt.Printf("error when parse template: %v\n", err)
  6. }
  7. name := "富贵🐷"
  8. // 渲染指定模板
  9. t.ExecuteTemplate(w, "index.html", name)
  10. }
  11. func main() {
  12. http.HandleFunc("/index", index)
  13. err := http.ListenAndServe(":9090", nil)
  14. if err != nil {
  15. fmt.Printf("HTTP server start failed, err:%v\n", err)
  16. }
  17. }

如果我们的模板名称冲突了,例如不同业务线下都定义了一个index.tmpl模板,我们可以通过下面两种方法来解决。

  1. 在模板文件开头使用{{define 模板名}}语句显式的为模板命名。
  2. 可以把模板文件存放在templates文件夹下面的不同目录中,然后使用template.ParseGlob(“templates/*/.tmpl”)解析模板。

标识符冲突解决

Go标准库的模板引擎使用的花括号{{和}}作为标识,而许多前端框架(如Vue和 AngularJS)也使用{{和}}作为标识符,所以当我们同时使用Go语言模板引擎和以上前端框架时就会出现冲突,这个时候我们需要修改标识符,修改前端的或者修改Go语言的。这里演示如何修改Go语言模板引擎默认的标识符:

  1. template.New("test").Delims("{[", "]}").ParseFiles("./t.tmpl")

text/template与html/template

html/template针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。

如果用户传入一个JS语句替换{{.}},那么页面可能会被改变。但是html/template会在页面上显示出转义后的JS内容。