Go中按用户类型条件渲染模板:解决模板未定义错误
我有一个网页包含匿名用户和管理员两种视图,仅需为管理员显示导航栏,其余内容对两类用户保持一致。以下是我尝试实现的代码:
main.go
package main import ( "log" "net/http" "text/template" "github.com/julienschmidt/httprouter" ) func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // Get the Basic Authentication credentials user, password, hasAuth := r.BasicAuth() if hasAuth && user == requiredUser && password == requiredPassword { // Delegate request to the given handle h(w, r, ps) } else { // Request Basic Authentication otherwise w.Header().Set("WWW-Authenticate", "Basic realm=Restricted") http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) } } } func Anonymous(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { t, err := template.ParseFiles("index.html") if err != nil { log.Fatalln(err) } err = t.Execute(w, map[string]string{"Name": "Anonymous"}) if err != nil { log.Fatalln(err) } } func Admin(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { t, err := template.ParseFiles("index.html", "admin.html") if err != nil { log.Fatalln(err) } err = t.Execute(w, map[string]string{"Name": "Admin"}) if err != nil { log.Fatalln(err) } } func main() { user := "admin" pass := "1234" router := httprouter.New() router.GET("/", Anonymous) router.GET("/admin/", BasicAuth(Admin, user, pass)) log.Fatal(http.ListenAndServe(":8080", router)) }
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{ .Name }}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script> <script> function counter() { document.getElementById("x").innerHTML = "x: " + document.querySelectorAll('.x').length; document.getElementById("y").innerHTML = "y: " + document.querySelectorAll('.y').length; document.getElementById("z").innerHTML = "z: " + document.querySelectorAll('.z').length; } </script> </head> <body onload="counter()"> {{ template "dashboard" }} <nav class="navbar fixed-bottom"> <div class="container-fluid nav-justified"> <span id="x" class="navbar-brand nav-item"></span> <span id="y" class="navbar-brand nav-item"></span> <span id="z" class="navbar-brand nav-item"></span> </div> </nav> </body> </html>
admin.html
{{ define "dashboard" }} <nav class="navbar"> <div class="container-fluid nav-justified"> <span class="nav-item"> <a class="navbar-brand" href="/a">a</a> </span> <span class="nav-item"> <a class="navbar-brand" href="/b">b</a> </span> <span class="nav-item"> <a class="navbar-brand" href="/c">c</a> </span> </div> </nav> {{ end }}
运行后遇到错误:
template: index.html:18:14: executing "index.html" at <{{template "dashboard"}}>: template "dashboard" not defined
我原本以为匿名用户渲染时未传入admin.html模板,dashboard模板不会被解析,但实际报错了,请问如何修复该问题,或是否有更优的实现方式?
方法一:为匿名用户补充空模板定义
Go模板执行时会严格校验所有引用的模板是否存在,哪怕你没打算渲染它。可以在index.html中默认定义一个空的dashboard模板,管理员加载admin.html时会自动覆盖这个空定义:
修改index.html,在文件末尾添加空模板:
<!DOCTYPE html> <html lang="en"> <!-- 原有头部、body内容不变 --> </html> {{ define "dashboard" }} <!-- 空内容,匿名用户无导航 --> {{ end }}
这样匿名用户解析index.html时就有了dashboard的定义,管理员加载admin.html后会用管理员导航模板替换空内容,满足需求。
方法二:用数据条件控制导航显示
放弃模板定义的方式,直接通过传入模板的字段判断是否渲染管理员导航:
修改index.html的body部分:
<body onload="counter()"> {{ if eq .Role "admin" }} <nav class="navbar"> <div class="container-fluid nav-justified"> <span class="nav-item"> <a class="navbar-brand" href="/a">a</a> </span> <span class="nav-item"> <a class="navbar-brand" href="/b">b</a> </span> <span class="nav-item"> <a class="navbar-brand" href="/c">c</a> </span> </div> </nav> {{ end }} <!-- 底部导航不变 --> </body>
修改main.go的处理器,统一解析index.html并传入角色字段:
func Anonymous(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { t, err := template.ParseFiles("index.html") if err != nil { log.Fatalln(err) } err = t.Execute(w, map[string]string{"Name": "Anonymous", "Role": "guest"}) if err != nil { log.Fatalln(err) } } func Admin(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { t, err := template.ParseFiles("index.html") if err != nil { log.Fatalln(err) } err = t.Execute(w, map[string]string{"Name": "Admin", "Role": "admin"}) if err != nil { log.Fatalln(err) } }
这种方式逻辑更直观,不需要额外的模板文件,适合简单场景。
方法三:预加载模板并通过参数控制渲染
生产环境推荐预加载所有模板,减少运行时解析开销,同时通过参数控制是否显示管理员导航:
修改main.go,预加载模板:
var tmpl *template.Template func init() { var err error tmpl, err = template.ParseFiles("index.html", "admin.html") if err != nil { log.Fatalln(err) } } func Anonymous(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { err := tmpl.ExecuteTemplate(w, "index.html", map[string]bool{"IsAdmin": false}) if err != nil { log.Fatalln(err) } } func Admin(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { err := tmpl.ExecuteTemplate(w, "index.html", map[string]bool{"IsAdmin": true}) if err != nil { log.Fatalln(err) } }
修改index.html的模板引用逻辑:
<body onload="counter()"> {{ if .IsAdmin }} {{ template "dashboard" }} {{ end }} <!-- 底部导航不变 --> </body>
这种方式只在程序启动时解析一次模板,性能更优,适合高并发场景。
内容的提问来源于stack exchange,提问作者user21795212

