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>