Go模板template用法详解
在Golang实战开发的过程中,我们经常会遇到一些这样那样的问题,然后要卡好半天,等问题解决了才发现原来一些细节知识点还是没有掌握好。今天golang学习网就整理分享《Go模板template用法详解》,聊聊模板,希望可以帮助到正在努力赚钱的你。
本文只介绍template的语法和用法,关于template包的函数、方法、template的结构和原理,见:深入解析Go template模板使用详解。
入门示例
以下为test.html文件的内容,里面使用了一个template语法{{.}}。
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Go Web</title>
{{ . }}
以下是test.html同目录下的一个go web程序:
package main
import (
"html/template"
"net/http"
)
func tmpl(w http.ResponseWriter, r *http.Request) {
t1, err := template.ParseFiles("test.html")
if err != nil {
panic(err)
}
t1.Execute(w, "hello world")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/tmpl", tmpl)
server.ListenAndServe()
}
前面的html文件中使用了一个template的语法{{.}},这部分是需要通过go的template引擎进行解析,然后替换成对应的内容。
在go程序中,handler函数中使用template.ParseFiles("test.html"),它会自动创建一个模板(关联到变量t1上),并解析一个或多个文本文件(不仅仅是html文件),解析之后就可以使用Execute(w,"hello world")去执行解析后的模板对象,执行过程是合并、替换的过程。例如上面的{{.}}中的.会替换成当前对象"hello world",并和其它纯字符串内容进行合并,最后写入w中,也就是发送到浏览器"hello world"。
本文不解释这些template包的函数、方法以及更底层的理论知识,本文只解释template的语法,如果觉得这些无法理解,或者看不懂官方手册,请看深入解析Go template模板使用详解。
关于点"."和作用域
在写template的时候,会经常用到"."。比如{{.}}、{{len .}}、{{.Name}}、{{$x.Name}}等等。
在template中,点"."代表当前作用域的当前对象。它类似于java/c++的this关键字,类似于perl/python的self。如果了解perl,它更可以简单地理解为默认变量$_。
例如,前面示例test.html中{{.}},这个点是顶级作用域范围内的,它代表Execute(w,"hello worold")的第二个参数"hello world"。也就是说它代表这个字符串对象。
再例如,有一个Person struct。
type Person struct {
Name string
Age int
}
func main(){
p := Person{"longshuai",23}
tmpl, _ := template.New("test").Parse("Name: {{.Name}}, Age: {{.Age}}")
_ = tmpl.Execute(os.Stdout, p)
}
这里{{.Name}}和{{.Age}}中的点"."代表的是顶级作用域的对象p,所以Execute()方法执行的时候,会将{{.Name}}替换成p.Name,同理{{.Age}}替换成{{p.Age}}。
但是并非只有一个顶级作用域,range、with、if等内置action都有自己的本地作用域。它们的用法后文解释,这里仅引入它们的作用域来解释"."。
例如下面的例子,如果看不懂也没关系,只要从中理解"."即可。
package main
import (
"os"
"text/template"
)
type Friend struct {
Fname string
}
type Person struct {
UserName string
Emails []string
Friends []*Friend
}
func main() {
f1 := Friend{Fname: "xiaofang"}
f2 := Friend{Fname: "wugui"}
t := template.New("test")
t = template.Must(t.Parse(
`hello {{.UserName}}!
{{ range .Emails }}
an email {{ . }}
{{- end }}
{{ with .Friends }}
{{- range . }}
my friend name is {{.Fname}}
{{- end }}
{{ end }}`))
p := Person{UserName: "longshuai",
Emails: []string{"a1@qq.com", "a2@gmail.com"},
Friends: []*Friend{&f1, &f2}}
t.Execute(os.Stdout, p)
}
输出结果:
hello longshuai! an email a1@qq.com an email a2@gmail.com my friend name is xiaofang my friend name is wugui
这里定义了一个Person结构,它有两个slice结构的字段。在Parse()方法中:
- 顶级作用域的
{{.UserName}}、{{.Emails}}、{{.Friends}}中的点都代表Execute()的第二个参数,也就是Person对象p,它们在执行的时候会分别被替换成p.UserName、p.Emails、p.Friends。 - 因为Emails和Friend字段都是可迭代的,在
{{range .Emails}}...{{end}}这一段结构内部an email {{.}},这个"."代表的是range迭代时的每个元素对象,也就是p.Emails这个slice中的每个元素。 - 同理,with结构内部
{{range .}}的"."代表的是p.Friends,也就是各个,再此range中又有一层迭代,此内层{{.Fname}}的点代表Friend结构的实例,分别是&f1和&f2,所以{{.Fname}}代表实例对象的Fname字段。
去除空白
template引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等等空白都原样保留。所以,对于要解析的内容,不要随意缩进、随意换行。
可以在{{符号的后面加上短横线并保留一个或多个空格"- "来去除它前面的空白(包括换行符、制表符、空格等),即{{- xxxx。
在}}的前面加上一个或多个空格以及一个短横线"-"来去除它后面的空白,即xxxx -}}。
例如:
{{23}} 23 23 23 23
<p>其中<code>{{23 -}}</code>中的短横线去除了这个替换结构后面的空格,即<code>}} 中间的空白。同理<code>{{- 45}}</code>的短横线去除了<code>中间的空白。</code></code></p>
<p>再看上一节的例子中:</p>
<pre class="brush:go;">t.Parse(
`hello {{.UserName}}!
{{ range .Emails }}
an email {{ . }}
{{- end }}
{{ with .Friends }}
{{- range . }}
my friend name is {{.Fname}}
{{- end }}
{{ end }}`)
注意,上面没有进行缩进。因为缩进的制表符或空格在替换的时候会保留。
第一行和第二行之间输出时会换行输出,不仅如此,range {{.Emails}}自身也占一行,在替换的时候它会被保留为空行。除非range前面没加{{-。由于range的{{- end加上了去除前缀空白,所以每次迭代的时候,每个元素之间都换行输出但却不多一空行,如果这里的end去掉{{-,则每个迭代的元素之间输出的时候都会有空行。同理后面的with和range。
注释
注释方式:{{/* a comment */}}。
注释后的内容不会被引擎进行替换。但需要注意,注释行在替换的时候也会占用行,所以应该去除前缀和后缀空白,否则会多一空行。
{{- /* a comment without prefix/suffix space */}}
{{/* a comment without prefix/suffix space */ -}}
{{- /* a comment without prefix/suffix space */ -}}
注意,应该只去除前缀或后缀空白,不要同时都去除,否则会破坏原有的格式。例如:
t.Parse(
`hello {{.UserName}}!
{{- /* this line is a comment */}}
{{ range .Emails }}
an email {{ . }}
{{- end }}
管道pipeline
pipeline是指产生数据的操作。比如{{.}}、{{.Name}}、funcname args等。
可以使用管道符号|链接多个命令,用法和unix下的管道类似:|前面的命令将运算结果(或返回值)传递给后一个命令的最后一个位置。
例如:
{{.}} | printf "%s\n" "abcd"
{{.}}的结果将传递给printf,且传递的参数位置是"abcd"之后。
命令可以有超过1个的返回值,这时第二个返回值必须为err类型。
需要注意的是,并非只有使用了|才是pipeline。Go template中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。这使得某些操作可以作为另一些操作内部的表达式先运行得到结果,就像是Unix下的命令替换一样。
例如,下面的(len "output")是pipeline,它整体先运行。
{{println (len "output")}}
下面是Pipeline的几种示例,它们都输出"output":
{{`"output"`}}
{{printf "%q" "output"}}
{{"output" | printf "%q"}}
{{printf "%q" (print "out" "put")}}
{{"put" | printf "%s%s" "out" | printf "%q"}}
{{"output" | printf "%s" | printf "%q"}}
变量
可以在template中定义变量:
// 未定义过的变量 $var := pipeline // 已定义过的变量 $var = pipeline
例如:
{{- $how_long :=(len "output")}}
{{- println $how_long}} // 输出6
再例如:
tx := template.Must(template.New("hh").Parse(
`{{range $x := . -}}
{{$y := 333}}
{{- if (gt $x 33)}}{{println $x $y ($z := 444)}}{{- end}}
{{- end}}
`))
s := []int{11, 22, 33, 44, 55}
_ = tx.Execute(os.Stdout, s)
输出结果:
44 333 444 55 333 444
上面的示例中,使用range迭代slice,每个元素都被赋值给变量$x,每次迭代过程中,都新设置一个变量$y,在内层嵌套的if结构中,可以使用这个两个外层的变量。在if的条件表达式中,使用了一个内置的比较函数gt,如果$x大于33,则为true。在println的参数中还定义了一个$z,之所以能定义,是因为($z := 444)的过程是一个Pipeline,可以先运行。
需要注意三点:
- 变量有作用域,只要出现end,则当前层次的作用域结束。内层可以访问外层变量,但外层不能访问内层变量。
- 有一个特殊变量
$,它代表模板的最顶级作用域对象(通俗地理解,是以模板为全局作用域的全局变量),在Execute()执行的时候进行赋值,且一直不变。例如上面的示例中,$ = [11 22 33 44 55]。再例如,define定义了一个模板t1,则t1中的$作用域只属于这个t1。 - 变量不可在模板之间继承。普通变量可能比较容易理解,但对于特殊变量"."和"$",比较容易搞混。见下面的例子。
例如:
func main() {
t1 := template.New("test1")
tmpl, _ := t1.Parse(
`
{{- define "T1"}}ONE {{println .}}{{end}}
{{- define "T2"}}{{template "T1" $}}{{end}}
{{- template "T2" . -}}
`)
_ = tmpl.Execute(os.Stdout, "hello world")
}
上面使用define额外定义了T1和T2两个模板,T2中嵌套了T1。{{template "T2" .}}的点代表顶级作用域的"hello world"对象。在T2中使用了特殊变量$,这个$的范围是T2的,不会继承顶级作用域"hello world"。但因为执行T2的时候,传递的是".",所以这里的$的值仍然是"hello world"。
不仅$不会在模板之间继承,.也不会在模板之间继承(其它所有变量都不会继承)。实际上,template可以看作是一个函数,它的执行过程是template("T2",.)。如果把上面的$换成".",结果是一样的。如果换成{{template "T2"}},则$=nil
如果看不懂这些,后文有解释。
条件判断
有以下几种if条件判断语句,其中第三和第四是等价的。
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
需要注意的是,pipeline为false的情况是各种数据对象的0值:数值0,指针或接口是nil,数组、slice、map或string则是len为0。
range...end迭代
有两种迭代表达式类型:
{{range pipeline}} T1 {{end}}
{{range pipeline}} T1 {{else}} T0 {{end}}
range可以迭代slice、数组、map或channel。迭代的时候,会设置"."为当前正在迭代的元素。
对于第一个表达式,当迭代对象的值为0值时,则range直接跳过,就像if一样。对于第二个表达式,则在迭代到0值时执行else语句。
tx := template.Must(template.New("hh").Parse(
`{{range $x := . -}}
{{println $x}}
{{- end}}
`))
s := []int{11, 22, 33, 44, 55}
_ = tx.Execute(os.Stdout, s)
需注意的是,range的参数部分是pipeline,所以在迭代的过程中是可以进行赋值的。但有两种赋值情况:
{{range $value := .}}
{{range $key,$value := .}}
如果range中只赋值给一个变量,则这个变量是当前正在迭代元素的值。如果赋值给两个变量,则第一个变量是索引值(map/slice是数值,map是key),第二个变量是当前正在迭代元素的值。
下面是在html中使用range的一个示例。test.html文件内容如下:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Go Web</title>
-
{{ range . }}
- {{ . }} {{ else }}
- Nothing to show {{ end}}
以下是test.html同目录下的go程序文件:
package main
import (
"html/template"
"net/http"
)
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
func process(w http.ResponseWriter, r *http.Request) {
t1 := template.Must(template.ParseFiles("test.html"))
s := []string{
"星期一",
"星期二",
"星期三",
"星期四",
"星期五",
"星期六",
"星期日",}
t1.Execute(w, s)
}
with...end
with用来设置"."的值。两种格式:
{{with pipeline}} T1 {{end}}
{{with pipeline}} T1 {{else}} T0 {{end}}
对于第一种格式,当pipeline不为0值的时候,点"."设置为pipeline运算的值,否则跳过。对于第二种格式,当pipeline为0值时,执行else语句块,否则"."设置为pipeline运算的值,并执行T1。
例如:
{{with "xx"}}{{println .}}{{end}}
上面将输出xx,因为"."已经设置为"xx"。
内置函数和自定义函数
template定义了一些内置函数,也支持自定义函数。关于如何自定义函数,见深入解析Go template模板使用详解。
以下是内置的函数列表:
and
返回第一个为空的参数或最后一个参数。可以有任意多个参数。
and x y等价于if x then y else x
not
布尔取反。只能一个参数。
or
返回第一个不为空的参数或最后一个参数。可以有任意多个参数。
"or x y"等价于"if x then x else y"。
print
printf
println
分别等价于fmt包中的Sprint、Sprintf、Sprintln
len
返回参数的length。
index
对可索引对象进行索引取值。第一个参数是索引对象,后面的参数是索引位。
"index x 1 2 3"代表的是x[1][2][3]。
可索引对象包括map、slice、array。
call
显式调用函数。第一个参数必须是函数类型,且不是template中的函数,而是外部函数。
例如一个struct中的某个字段是func类型的。
"call .X.Y 1 2"表示调用dot.X.Y(1, 2),Y必须是func类型,函数参数是1和2。
函数必须只能有一个或2个返回值,如果有第二个返回值,则必须为error类型。
除此之外,还内置一些用于比较的函数:
eq arg1 arg2:
arg1 == arg2时为true
ne arg1 arg2:
arg1 != arg2时为true
lt arg1 arg2:
arg1 arg2时为true
ge arg1 arg2:
arg1 >= arg2时为true
对于eq函数,支持多个参数:
eq arg1 arg2 arg3 arg4...
它们都和第一个参数arg1进行比较。它等价于:
arg1==arg2 || arg1==arg3 || arg1==arg4
示例:
{{ if (gt $x 33) }}{{println $x}}{{ end }}
嵌套template:define和template
define可以直接在待解析内容中定义一个模板,这个模板会加入到common结构组中,并关联到关联名称上。如果不理解,还是建议阅读深入解析Go template模板使用详解。
定义了模板之后,可以使用template这个action来执行模板。template有两种格式:
{{template "name"}}
{{template "name" pipeline}}
第一种是直接执行名为name的template,点设置为nil。第二种是点"."设置为pipeline的值,并执行名为name的template。可以将template看作是函数:
template("name)
template("name",pipeline)
例如:
func main() {
t1 := template.New("test1")
tmpl, _ := t1.Parse(
`{{- define "T1"}}ONE {{println .}}{{end}}
{{- define "T2"}}TWO {{println .}}{{end}}
{{- define "T3"}}{{template "T1"}}{{template "T2" "haha"}}{{end}}
{{- template "T3" -}}
`)
_ = tmpl.Execute(os.Stdout, "hello world")
}
输出结果:
ONE <nil> TWO haha </nil>
上面定义了4个模板,一个是test1,另外三个是使用define来定义的T1、T2、T3,其中t1是test1模板的关联名称。T1、T2、T3和test1共享一个common结构。其中T3中包含了执行T1和T2的语句。最后只要{{template T3}}就可以执行T3,执行T3又会执行T1和T2。也就是实现了嵌套。此外,执行{{template "T1"}}时,点设置为nil,而{{temlate "T2" "haha"}}的点设置为了"haha"。
注意,模板之间的变量是不会继承的。
下面是html文件中嵌套模板的几个示例。
t1.html文件内容如下:
<meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=9"><title>Go Web Programming</title><div> This is t1.html before</div>
<div>This is the value of the dot in t1.html - [{{ . }}]</div>
<hr>
{{ template "t2.html" }}
<hr><div> This is t1.html after</div>
因为内部有{{template "t2.html"}},且此处没有使用define去定义名为"t2.html"的模板,所以需要加载解析名为t2.html的文件。t2.html文件内容如下:
<div style="background-color: yellow;">
This is t2.html<br>
This is the value of the dot in t2.html - [{{ . }}]
</div>
处理这两个文件的handler函数如下:
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("t1.html", "t2.html")
t.Execute(w, "Hello World!")
}
上面也可以不额外定义t2.html文件,而是直接在t1.html文件中使用define定义一个模板。修改t1.html文件如下:
<meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=9"><title>Go Web Programming</title><div> This is t1.html before</div>
<div>This is the value of the dot in t1.html - [{{ . }}]</div>
<hr>
{{ template "t2.html" }}
<hr><div> This is t1.html after</div>
{{define "t2.html"}}
<div style="background-color: yellow;">
This is t2.html<br>
This is the value of the dot in t2.html - [{{ . }}]
</div>
{{end}}
然后在handler中,只需解析t1.html一个文件即可。
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("t1.html")
t.Execute(w, "Hello World!")
}
block块
{{block "name" pipeline}} T1 {{end}}
A block is shorthand for defining a template
{{define "name"}} T1 {{end}}
and then executing it in place
{{template "name" pipeline}}
The typical use is to define a set of root templates that are
then customized by redefining the block templates within.
根据官方文档的解释:block等价于define定义一个名为name的模板,并在"有需要"的地方执行这个模板,执行时将"."设置为pipeline的值。
但应该注意,block的第一个动作是执行名为name的模板,如果不存在,则在此处自动定义这个模板,并执行这个临时定义的模板。换句话说,block可以认为是设置一个默认模板。
例如:
{{block "T1" .}} one {{end}}
它首先表示{{template "T1" .}},也就是说先找到T1模板,如果T1存在,则执行找到的T1,如果没找到T1,则临时定义一个{{define "T1"}} one {{end}},并执行它。
下面是正常情况下不使用block的示例。
home.html文件内容如下:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Go Web Programming</title>
{{ template "content" }}
在此文件中指定了要执行一个名为"content"的模板,但此文件中没有使用define定义该模板,所以需要在其它文件中定义名为content的模板。现在分别在两个文件中定义两个content模板:
red.html文件内容如下:
{{ define "content" }}
<h1 style="color: red;">Hello World!</h1>
{{ end }}
blue.html文件内容如下:
{{ define "content" }}
<h1 style="color: blue;">Hello World!</h1>
{{ end }}
在handler中,除了解析home.html,还根据需要解析red.html或blue.html:
func process(w http.ResponseWriter, r *http.Request) {
rand.Seed(time.Now().Unix())
t := template.New("test")
if rand.Intn(10) > 5 {
t, _ = template.ParseFiles("home.html", "red.html")
} else {
t, _ = template.ParseFiles("home.html", "blue.html")
}
t.Execute(w,"")
}
如果使用block,那么可以设置默认的content模板。例如将原本定义在blue.html中的content设置为默认模板。
修改home.html:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Go Web Programming</title>
{{ block "content" . }}
<h1 style="color: blue;">Hello World!</h1>
{{ end }}
然后修改handler:
func process(w http.ResponseWriter, r *http.Request) {
rand.Seed(time.Now().Unix())
t := template.New("test")
if rand.Intn(10) > 5 {
t, _ = template.ParseFiles("home.html", "red.html")
} else {
t, _ = template.ParseFiles("home.html")
}
t.Execute(w,"")
}
当执行else语句块的时候,发现home.html中要执行名为content的模板,但在ParseFiles()中并没有解析包含content模板的文件。于是执行block定义的content模板。而执行非else语句的时候,因为red.html中定义了content,会直接执行red.html中的content。
block通常设置在顶级的根文件中,例如上面的home.html中。
html/template的上下文感知
对于html/template包,有一个很好用的功能:上下文感知。text/template没有该功能。
上下文感知具体指的是根据所处环境css、js、html、url的path、url的query,自动进行不同格式的转义。
例如,一个handler函数的代码如下:
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("test.html")
content := `I asked: <i>"What's up?"</i>`
t.Execute(w, content)
}
上面content是Execute的第二个参数,它的内容是包含了特殊符号的字符串。
下面是test.html文件的内容:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Go Web Programming</title><div>{{ . }}</div>
<div><a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycJx-aYKks5OvpIGceNquiHrbgrfIfoK6lHWrq32errKGm4qNimyyp7ikg4aJ0LGedpeR0LGyh7qXZbF5ha6za4WqfWuOab7dwKKDg3Si' rel='nofollow'>Path</a></div>
<div><a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycF-TjWmks5OvpIJigNGxnnLZgretsIK6lHWshpR5rrKFmnmyh6O_t7dsgXaJ0bOIg8-FzalskdN9qbGGl2m0gI2qfmuGsrKVu2mNrJHPs4VuoQ' rel='nofollow'>Query</a></div>
<div><a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycXp6jYKvvru0qo6JiJuuiHrbgreyo5m5haq8hYWtsqWcn4mRiqSyzcqyfoaA3MiMcpOCt62ygrp-mq5km6Cya42aebKGoq6VuKSCdp7csnuGmYaqtbKFupSZu6x5abR9o2SKjYZrvt2zso6Gjc-yoX6Z' rel='nofollow'>
Path
</a>
</div>
<div>
<a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycF-TjWqCr7evr42Khde-i4LQhZWppYXQcZyvZH6kr42GnHqNgrG7lc6gmHWM3rJnftCFzaZqm6mFqrybha2yoo2qibKOsb_Q0aSBZIzPra1-zoGVtqWGqpensHmFabOAial9jZ2hvt2vbYN2opm-iH6Xkd2xs5K6hpqwn31p' rel='nofollow'>
Query
</a>
</div>
<div>
<a onclick="f('I asked: \x3ci\x3e\x22What\x27s up?\x22\x3c\/i\x3e')">
Onclick
</a>
</div>
不转义
上下文感知的自动转义能让程序更加安全,比如防止XSS攻击(例如在表单中输入带有的内容并提交,会使得用户提交的这部分script被执行)。
如果确实不想转义,可以进行类型转换。
type CSS type HTML type JS type URL
转换成指定个时候,字符都将是字面意义。
例如:
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
t.Execute(w, template.HTML(r.FormValue("comment")))
}
更多关于Go模板template用法详解请查看下面的相关链接
今天关于《Go模板template用法详解》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
深入解析Gotemplate模板使用详解
- 上一篇
- 深入解析Gotemplate模板使用详解
- 下一篇
- go语言beego框架分页器操作及接口频率限制示例
-
- Golang · Go教程 | 47秒前 |
- Go语言CSV字段强制引号设置教程
- 369浏览 收藏
-
- Golang · Go教程 | 2分钟前 | golang 重试机制 指数退避 context.Context 系统健壮性
- Golang实现指数退避重试机制
- 477浏览 收藏
-
- Golang · Go教程 | 12分钟前 |
- Golangreflect调用私有方法详解
- 343浏览 收藏
-
- Golang · Go教程 | 16分钟前 |
- Golang记录调用堆栈方法解析
- 366浏览 收藏
-
- Golang · Go教程 | 41分钟前 |
- Golang库快速安装方法分享
- 480浏览 收藏
-
- Golang · Go教程 | 42分钟前 |
- Vim运行Go代码,提升开发效率
- 462浏览 收藏
-
- Golang · Go教程 | 52分钟前 |
- GolangWebAPI设计与错误处理方法
- 490浏览 收藏
-
- Golang · Go教程 | 1小时前 | golang 日志优化
- Golang日志优化技巧分享
- 428浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- VSCode配置Go插件及自动补全教程
- 228浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Golang变量的零值是什么?
- 342浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Golang大文件读取优化技巧分享
- 136浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Golang性能测试技巧与常见陷阱
- 107浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3178次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3389次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3418次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4523次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3797次使用
-
- 深入解析Gotemplate模板使用详解
- 2022-12-28 206浏览
-
- 详解golang 模板(template)的常用基本语法
- 2022-12-24 286浏览

