1. 程式人生 > 其它 >6-Gin模板渲染

6-Gin模板渲染

一 基本使用

第一步:index.html

在專案根路徑下新建templates資料夾,資料夾內寫模板檔案,如index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>第一個模板檔案</title>
</head>
<body>
我的名字是:{{.name}}
<br>
我的年齡是:{{.age}}
</body>
</html>

第二步:渲染模板

Gin 框架中使用 c.HTML 可以渲染模板,渲染模板前需要使用 LoadHTMLGlob()或者 LoadHTMLFiles()方法載入模板

package main

import (
	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	// 載入整個資料夾
	//router.LoadHTMLGlob("templates/*")
  // 載入單個
	router.LoadHTMLFiles("templates/index.html", "templates/index2.html")
	router.GET("/index", func(c *gin.Context) {

		c.HTML(200,"index.html",gin.H{"name":"劉清政","age":19})

	})
	router.Run(":8000")
}

二 模板檔案放在不同資料夾下

//Gin 框架中如果不同目錄下面有同名模板的話我們需要使用下面方法載入模板 
// 一旦templates資料夾下還有資料夾,一定要按給每一個都定義名字
//注意:定義模板的時候需要通過 define 定義名稱
templates/admin/index.html
{{ define "admin/index.html" }}
html內容
{{end}}

2.1 main.go

package main

import (
	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	// 注意此處的匯入路徑
	router.LoadHTMLGlob("templates/**/*")
	router.GET("/index", func(c *gin.Context) {
    // 模板名為新定義的模板名字
		c.HTML(200,"admin/index.tpl",gin.H{"title":"我是後臺模板"})

	})
	router.Run(":8000")
}


2.2 admin/index.tmpl

{{ define "admin/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>後臺管理首頁</title>
</head>
<body>
<h1>{{.title}}</h1>
</body>
</html>

{{end}}

2.3 目錄結構為

2.4 注意

// 1 如果模板在多級目錄裡面的話需要這樣配置 r.LoadHTMLGlob("templates/**/**/*") /** 表示目錄
// 2 LoadHTMLGlob只能載入同一層級的檔案
比如說使用router.LoadHTMLFile("/templates/**/*"),就只能載入/templates/admin/或者/templates/order/下面的檔案
解決辦法就是通過filepath.Walk來搜尋/templates下的以.html結尾的檔案,把這些html檔案都載入一個數組中,然後用LoadHTMLFiles載入
var files []string
filepath.Walk("./templates", func(path string, info os.FileInfo, err error) error {
		if strings.HasSuffix(path, ".html") {
			files = append(files, path)
		}
		return nil
	})
router.LoadHTMLFiles(files...)

三 模板語法

3.1 {{.}} 渲染變數

有兩個常用的傳入變數的型別。一個是struct,在模板內可以讀取該struct的欄位(對外暴露的屬性)來進行渲染。還有一個是map[string]interface{},在模板內可以使用key獲取對應的value來進行渲染

3.1.1 main.go

package main

import (
	"github.com/gin-gonic/gin"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	router := gin.Default()

	var files []string
	filepath.Walk("./templates", func(path string, info os.FileInfo, err error) error {
		if strings.HasSuffix(path, ".html") {
			files = append(files, path)
		}
		return nil
	})
	router.LoadHTMLFiles(files...)

	router.GET("/index", func(c *gin.Context) {
		type Book struct {
			Name string
			price int
		}
		c.HTML(200, "order.html", gin.H{
			"age": 10,
			"name":"劉清政",
			"hobby":[3]string{"抽菸","喝酒","燙頭"},
			"wife":[]string{"劉亦菲","迪麗熱巴","古力娜扎"},
			"info":map[string]interface{}{"height":180,"gender":"男"},
			"book":Book{"紅樓夢",99},

		})

	})
	router.Run(":8000")
}

3.1.2 order.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>訂單頁面</title>
</head>
<body>
<h1>渲染字串,數字,陣列,切片,maps,結構體</h1>
<p>年齡:{{.age}}</p>
<p>姓名:{{.name}}</p>
<p>愛好:{{.hobby}}</p>
<p>wife:{{.wife}}</p>
<p>資訊:{{.info}}--->{{.info.gender}}</p>
<p>圖書:{{.book}}--->{{.book.Name}}</p>
</body>
</html>

3.2 註釋

{{/* a comment */}}
// 註釋,執行時會忽略。可以多行。註釋不能巢狀,並且必須緊貼分界符始止。
<p>圖書不顯示,註釋了:{{/* .book */}}</p>

3.3 宣告變數

<h1>宣告變數</h1>
<p>{{$obj := .book.Name}}</p>
<p>{{$obj}}</p>

3.4 移除空格

{{符號的後面加上短橫線並保留一個或多個空格來去除它前面的空白(包括換行符、製表符、空格等),即{{- xxxx

}}的前面加上一個或多個空格以及一個短橫線-來去除它後面的空白,即xxxx -}}

<p>{{ 20 }} < {{ 40 }}---> 20 < 40</p>
<p>{{ 20 -}} < {{- 40 }}-->20<40</p>

3.5 比較函式

布林函式會將任何型別的零值視為假,其餘視為真。 下面是定義為函式的二元比較運算的集合:

eq  如果arg1 == arg2則返回真
ne  如果arg1 != arg2則返回真
lt  如果arg1 < arg2則返回真
le  如果arg1 <= arg2則返回真
gt  如果arg1 > arg2則返回真
ge  如果arg1 >= arg2則返回真
// 使用方式
<h1>比較函式</h1>
<p>{{gt 11 13}}</p>
<p>{{lt 11 13}}</p>
<p>{{eq 11 11}}</p>

3.6 條件判斷

// 方式一
{{if pipeline}} T1 {{end}}
// 方式二
{{if pipeline}} T1 {{else}} T0 {{end}}
// 方式三
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}} 

<h1>條件判斷</h1>
<br>//案例一
{{if .show}}
看到我了
{{end}}

<br>//案例二
{{if gt .age 18}}
成年了
{{else}}
沒成年
{{end}}

<br>// 案例三
{{if gt .score 90}}
優秀
{{else if gt .score 60}}
及格
{{else}}
不及格
{{end}}

3.7 range迴圈

// 方式一
{{ range pipeline }} T1 {{ end }}

// 方式二
// 如果 pipeline 的長度為 0 則輸出 else 中的內容
{{ range pipeline }} T1 {{ else }} T2 {{ end }}

range可以遍歷slice、陣列、map或channel。遍歷的時候,會設定.為當前正在遍歷的元素。

對於第一個表示式,當遍歷物件的值為0值時,則range直接跳過,就像if一樣。對於第二個表示式,則在遍歷到0值時執行else。

range的引數部分是pipeline,所以在迭代的過程中是可以進行賦值的。但有兩種賦值情況:

{{ range $value := pipeline }} T1 {{ end }}
{{ range $key, $value := pipeline }} T1 {{ end }}

如果range中只賦值給一個變數,則這個變數是當前正在遍歷元素的值。如果賦值給兩個變數,則第一個變數是索引值(array/slice是數值,map是key),第二個變數是當前正在遍歷元素的值

<h1>range迴圈</h1>
<h2>迴圈陣列</h2>
{{range $index,$value:=.wife}}
<p>{{$index}}---{{$value}}</p>
{{end}}
<h2>迴圈map</h2>
{{range $key,$value:=.info}}
<p>{{$key}}---{{$value}}</p>
{{end}}
<h2>迴圈空-->"girls":map[string]interface{}{}</h2>
{{range $value:=.girls}}
<p>{{$value}}</p>
{{else}}
沒有女孩
{{end}}

3.8 with...end

{{ with pipeline }} T1 {{ end }}
{{ with pipeline }} T1 {{ else }} T0 {{ end }}

對於第一種格式,當pipeline不為0值的時候,將.設定為pipeline運算的值,否則跳過。
對於第二種格式,當pipeline為0值時,執行else語句塊T0,否則.設定為pipeline運算的值,並執行T1。

<h1>with ... end</h1>
<h2>不使用with</h2>
<p>{{.book.Name}}</p>
<p>{{.book.Price}}</p>
<h2>使用with</h2>
{{with .book}}
<p>{{.Name}}</p>
<p>{{.Price}}</p>
{{end}}

3.9 函式

golang的模板其實功能很有限,很多複雜的邏輯無法直接使用模板語法來表達,所以只能使用模板函式來實現。

首先,template包建立新的模板的時候,支援.Funcs方法來將自定義的函式集合匯入到該模板中,後續通過該模板渲染的檔案均支援直接呼叫這些函式。

該函式集合的定義為:

type FuncMap map[string]interface{}

key為方法的名字,value則為函式。這裡函式的引數個數沒有限制,但是對於返回值有所限制。有兩種選擇,一種是隻有一個返回值,還有一種是有兩個返回值,但是第二個返回值必須是error型別的。這兩種函式的區別是第二個函式在模板中被呼叫的時候,假設模板函式的第二個引數的返回不為空,則該渲染步驟將會被打斷並報錯

3.9.1 內建函式

var builtins = FuncMap{
	// 返回第一個為空的引數或最後一個引數。可以有任意多個引數。
	// "and x y"等價於"if x then y else x"
	"and": and,
	// 顯式呼叫函式。第一個引數必須是函式型別,且不是template中的函式,而是外部函式。
	// 例如一個struct中的某個欄位是func型別的。
	// "call .X.Y 1 2"表示呼叫dot.X.Y(1, 2),Y必須是func型別,函式引數是1和2。
	// 函式必須只能有一個或2個返回值,如果有第二個返回值,則必須為error型別。
	"call": call,
	// 返回與其引數的文字表示形式等效的轉義HTML。
	// 這個函式在html/template中不可用。
	"html": HTMLEscaper,
	// 對可索引物件進行索引取值。第一個引數是索引物件,後面的引數是索引位。
	// "index x 1 2 3"代表的是x[1][2][3]。
	// 可索引物件包括map、slice、array。
	"index": index,
	// 返回與其引數的文字表示形式等效的轉義JavaScript。
	"js": JSEscaper,
	// 返回引數的length。
	"len": length,
	// 布林取反。只能一個引數。
	"not": not,
	// 返回第一個不為空的引數或最後一個引數。可以有任意多個引數。
	// "or x y"等價於"if x then x else y"。
	"or":      or,
	"print":   fmt.Sprint,
	"printf":  fmt.Sprintf,
	"println": fmt.Sprintln,
	// 以適合嵌入到網址查詢中的形式返回其引數的文字表示的轉義值。
	// 這個函式在html/template中不可用。
	"urlquery": URLQueryEscaper,
}
<h1>內建函式</h1>
<p>{{len .name}}-->位元組數</p>

3.9.3 比較函式

3.5模組學過了

3.9.2 自定義函式

// 第一步:定義一個函式
func parserTime(t int64) string {
	return time.Unix(t, 0).Format("2006年1月2日 15點04分05秒")
}
//第二步:在載入模板之前執行
router := gin.Default()
	router.SetFuncMap(template.FuncMap{
		"parserTime": parserTime,
	})
//第三步:在模板中使用-->"date":   time.Now().Unix(),
<h1>自定義模板函式</h1>
<p>不使用自定義模板函式:{{.date}}</p>
<p>使用自定義模板函式:{{parserTime .date}}</p>
</body>

3.10 模板巢狀

3.10.1 define

define可以直接在待解析內容中定義一個模板

// 定義名稱為name的template
{{ define "name" }} T {{ end }}

3.10.2 template

使用template來執行模板

// 執行名為name的template
{{ template "name" }}  // 不加.,不能使用當前頁面的變數渲染define定義的模板
{{ template "name"  . }} // 加入點,可以使用當前頁面的變數渲染define定義的模板

案例

header.html
{{define "header.html"}}
<style>
    h1{
        background: pink;
        color: aqua;
        text-align: center;
    }
</style>
<h1>我是一個頭部--{{.header}}</h1>
{{end}}
footer.html
{{define "footer.html"}}
<style>
    h1 {
        background: pink;
        color: aqua;
        text-align: center;
    }
</style>
<h1>我是一個尾部--{{.footer}}</h1>
{{end}}
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>第一個模板檔案</title>
</head>
<body>
{{ template "header.html" .}}
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
{{ template "footer.html" .}}
</body>
</html>
main.go
	router.GET("/index", func(c *gin.Context) {
		c.HTML(200, "index.html", gin.H{
			"header":    "頭部頭部",
			"footer":   "尾部尾部",
		})
	})

3.11 模板繼承

通過block、define、template實現模板繼承。 block

{{ block "name" pipeline }} T {{ end }}

block等價於define定義一個名為name的模板,並在"有需要"的地方執行這個模板,執行時將.設定為pipeline的值。

等價於:先 {{ define "name" }} T {{ end }} 再執行 {{ template "name" pipeline }}

3.11.1 base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .head {
            height: 50px;
            background-color: red;
            width: 100%;
            text-align: center;
        }
        .main {
            width: 100%;
        }
        .main .left {
            width: 30%;
            height: 1000px;
            float: left;
            background-color:violet;
            text-align: center;
        }
        .main .right {
            width: 70%;
            float: left;
            text-align: center;
            height: 1000px;
            background-color:yellowgreen;
        }
    </style>
</head>
<body>
<div class="head">
    <h1>頂部標題部分</h1>
</div>
<div class="main">
    <div class="left">
        <h1>左側側邊欄</h1>
    </div>
    <div class="right">
        {{ block "content" . }}
        <h1>預設顯示內容</h1>
        {{ end }}
    </div>
</div>
</body>
</html>

3.11.2 home.html

{{ template "base.html" . }}

{{ define "content" }}
<h1>{{.s}}</h1>

{{ end }}

3.11.3 goods.html

{{ template "base.html" . }}

{{ define "content" }}
<h1>{{.s}}</h1>
{{ end }}

3.11.4 main.go

	router.GET("/goods", func(c *gin.Context) {
		c.HTML(200, "goods.html", gin.H{
			"s":    "這是商品goods頁面",
		})
	})
	router.GET("/home", func(c *gin.Context) {
		c.HTML(200, "home.html", gin.H{
			"s":    "這是首頁,home",
		})
	})

3.12 修改預設識別符號

Go標準庫的模板引擎使用的花括號{{}}作為標識,而許多前端框架(如Vue和 AngularJS)也使用{{}}作為識別符號,所以當我們同時使用Go語言模板引擎和以上前端框架時就會出現衝突,這個時候我們需要修改識別符號,修改前端的或者修改Go語言的。這裡演示如何修改Go語言模板引擎預設的識別符號:

router.Delims("[[","]]")

3.13 xss攻擊

// 1 定義函式
func safe (str string) template.HTML {
	return template.HTML(str)
}
//2 註冊函式
	router.SetFuncMap(template.FuncMap{
		"parserTime": parserTime,
		"safe": safe,
	})

// 3 模板中使用
<h1>xss攻擊</h1>
<p>{{.str1}}</p>
<p>{{safe .str1 }}</p>