go-zero本身支持html模板解析,我們只需要添加url對應模板解hanlder,實現邏輯就可以了
但是winlion太懶了,我甚至想
- 不寫任何一個和模板相關的handler
- 如果有新的模板,直接把模板到某個特定目錄就好,不要動任何go代碼
- 在開發環境下沒有緩存,修改了模板文件無需重啓
需求在這裏,開擼吧
在代碼開始前,你可能需要閱讀
金光燦燦的Gorm V2+適合創業的golang微服務框架go-zero實戰 如果對go-zero已經瞭解,直接跳過吧
創建項目
生成go.mod文件
以如下指令創建項目
mkdir html
cd html
go mod init html
定義html.api
本文設計API如下 |描述|格式|方法|參數|返回|是否需要鑑權| |----|----|----|----|----|----| |用戶登錄|/open/authorization|post|mobile:手機號,passwd:密碼,code:圖片驗證碼|id:用戶ID,token:用戶token|否|
根據以上描述,書寫api的模板文件如下
type (
UserOptReq struct {
mobile string `form:"mobile"`
passwd string `form:"passwd"`
code string `form:"code,optional"`
}
UserOptResp struct {
id uint `json:"id"`
token string `json:"token"`
}
)
service html-api {
@server(
handler: authorizationHandler
folder: open
)
post /open/authorization(UserOptReq) returns(UserOptResp)
}
注意
- 本文和html模板相關,可以不適用goctl工具
- 但是由於使用工具可以爲我們節省很多搭建框架相關的工作,所以建議使用用ctl生成
生成代碼
採用如下指令生成代碼
goctl api go -api html.api -dir .
此時用go run html.go
指令可以發現系統以及運行
html模板自動解析實現思路
模板解析需要了解如下倆個已知知識點
- html網頁輸出本質上是get請求輸出
- 相對於一個項目來說,模板文件個數是有限的,因此我們可以將模板枚舉出來,完成訪模板名稱和請求之間的映射
對於第一個,我們可以構建get路由來實現請求,以首頁請求http://127.0.0.1:8888/index.html
爲例,核心代碼如下,
htmltplrouter:= rest.Route{
Method: http.MethodGet,
Path: "/index.html",
Handler: htmlhandler(...),
}
engine.AddRoute(htmltplrouter)
在上述代碼中,htmlhandler
函數實現了對請求的響應,也就是解析了模板並將模板內容輸出
//gloabtemplate:全局解析的模板參數
//tplname:模板名稱,
//serverCtx 應用配置
func htmlhandler(gloabtemplate *template.Template, tplname string, serverCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//模板名字就是r.URL.Path
t := gloabtemplate
//如果是調試模式,則支持熱解析
if serverCtx.Config.Debug {
t, _ = template.New("").Funcs(FuncMap()).ParseGlob(serverCtx.Config.TemplatePattern)
}
err := t.ExecuteTemplate(w, tplname, r.URL.Query())
if err != nil {
httpx.Error(w, err)
}
}
}
如何建立uri和模板名稱之間的映射關係
這裏有幾個點需要強調:
- 在golang中,每個包含模板內容的html文件會被解析成一個模板,如在
view/www/
下新建test.html
文件,即使裏面沒有內容,系統也會將其解析得到一個名叫test.html
的模板。 - 如果在模板文件以template標籤中定義名稱爲
www/test.html
的模板,則系統又會解析得到一個名叫www/test.html
的模板,此時存在倆個模板,一個名叫test.html
,一個名叫www/test.html
view/www/test.html
文件內容如下
{{define "www/test.html"}}
<h1>這是模板www/test.html的內容</h1>
{{end}}
因此我們可以取巧,將模板名稱命名成需要建立映射關係的uri 比如外部通過http://127.0.0.1:8888/www/test.html
來訪問,此時req.URI.path爲/www/test.html
我們可以用這個作爲模板名稱
如何枚舉模板
這裏用到了ParseGlob
函數,這個函數本質上是對filepath.ParseGlob()
和template.ParseFiles()
的封裝,可以遍歷滿足一定格式的路徑的所有文件,假設我們建立模板存放目錄internal\view
如下
tree /F /A
| go.mod
| go.sum
| html.api
| html.go
| readme.md
|
+---etc
| html-api.yaml
|
\---internal
+---config
| config.go
|
+---handler
| | routes.go
| |
| \---open
| authorizationhandler.go
|
+---logic
| \---open
| authorizationlogic.go
|
+---svc
| servicecontext.go
|
+---types
| types.go
|
\---view
+---public
| footer.html
| header.html
|
\---www
index.html
test.html
則我們可以使用格式字符串 ./internal/view/**/*
來遍歷並解析並解析模板,建立模板和uri之間的對應關係,核心代碼如下
gloabtemplate,err:=template.New("").Funcs(FuncMap()).ParseGlob("./internal/view/**/*")
//range輪詢
for _, tpl := range gloabtemplate.Templates() {
patern := tpl.Name()
if !strings.HasPrefix(patern, "/") {
patern = "/" + patern
}
//首頁默認index.html index.htm index.php
tplname := tpl.Name()
if 0 == len(tplname) {
tplname = serverCtx.Config.TemplateIndex
}
pageRouters = append(pageRouters, rest.Route{
Method: http.MethodGet,
Path: patern,
Handler: htmlhandler(gloabtemplate, tplname, serverCtx),
})
logx.Infof("register page %s %s", patern, tplname)
}
//添加到engin路由中
engine.AddRoutes(pageRouters)
如何在模板中使用函數
有時候我們需要在模板中使用函數,則需要用到函數映射功能,golang提供接口函數Funcs()
來注入,
假設我們需要在/www/version.html中查看系統版本,應該怎麼做呢?
- 定義相關函數
//handlers\funcs.go
package handler
import (
"html/template"
)
//定義
var funcsMap template.FuncMap = make(template.FuncMap)
func FuncMap() template.FuncMap {
funcsMap["version"] = version
funcsMap["hello"] = hello
return funcsMap
}
func version() string {
//這個函數返回當前版本號0.0.1
return "0.01"
}
func hello(str string) string {
//這個函數返回當前版本號0.0.1
return "hello "+ str
}
應用可以通過 template.New("").Funcs(FuncMap())
來注入響應函數
- 定義模板文件 新建文件
view/www/version.html
,內容如下
{{define "www/version.html"}}
<h1>當前版本號:{{version}}</h1>
<h1>這裏測試帶參數的函數:{{hello "word"}}</h1>
{{end}}
-
無參數的函數展示 此時模板文件中通過
{{version}}
即可調用並顯示版本號0.01 -
有參數的函數 對應有參數的函數,按照參數順序排列,中間用空格隔開
-
以上顯示結果
當前版本號:0.01
這裏測試帶參數的函數:hello word
如何模板嵌套
使用templete
指令進行嵌套
新建view/public/header.html
內容如下
<!-- 頂部菜單 Start -->
<div class="top-menu-wrapper index-menu">
<h1>這是Head</h1>
</div>
新建view/public/footer.html
內容如下
<!-- 頂部菜單 Start -->
<div class="top-menu-wrapper index-menu">
<h1>這是footer</h1>
</div>
新建view/www/index.html
文件,內容如下
<!DOCTYPE html>
<html>
<head></head>
<body>
{{template "header.html" .}}
<div class="content-box" data-spy="scroll" data-target=".section-scrollspy">
<h1>這是Index的內容</h1>
</div>
{{template "footer.html" .}}
</body>
</html>
此時編譯後即可得到如下內容
這是Head 這是Index的內容 這是footer
如何在模板中使用變量
- 在模板中直接使用 首先需要將變量暴露到模板中,這裏我們使用到了
ExecuteTemplate
函數,該函數第三個參數即可以在模板裏面訪問的參數,比如如下代碼,則在模板中可以訪問Query了
data := r.URI.Query
err := t.ExecuteTemplate(w, tplname, data)
新建view/www/arg.html
文件
{{define "www/arg.html"}}
<h5>arga={{.arga}}</h5>
<h5>argb={{.argb}}</h5>
{{end}}
請求訪問方式http://127.0.0.1:8888/www/arg.html?arga=123&argb=456
系統返回結果
arga=[123]
argb=[456]
- 在嵌套模板中使用
在嵌套模板中使用需要將對象傳入,方式是在模板名後加一個.
,如下 新建view/www/embd.html
文件
{{define "www/embd.html"}}
沒加點:{{template "www/arg.html"}}
=======
加點:{{template "www/arg.html" .}}
{{end}}
結果如下
沒加點:
<h5>arga=</h5>
<h5>argb=</h5>
=======
加點:
<h5>arga=[123]</h5>
<h5>argb=[456]</h5>
如何實現模板熱更新
假設我們的應用支持開發模式和生產模式,在生產模式下,由於有性能考慮,系統不需要每次訪問都解析模板。而在開發模式下,每個模板有所任何小的修改,我們都希望模板能自動更新,怎麼實現這個功能呢? 方案很多,有文件監聽方案,如github.com/fsnotify/fsnotify
監聽模板目錄,也有標記位方案,無論模板有沒有變動,只要是開發模式,每次請求都重新加載模板並解析,gin
就是這種方案,本文也採用這種方案,核心代碼如下
//模板名字就是r.URL.Path
t := gloabtemplate
//如果是debug模式
if serverCtx.Config.Debug {
//每次都重新解析
t, _ = template.New("").Funcs(FuncMap()).ParseGlob(serverCtx.Config.TemplatePattern)
}
err := t.ExecuteTemplate(w, tplname, r.URL.Query())
如何設置首頁
本質上是指定/
請求對應的模板,以及系統錯誤對應的模板
for _, tpl := range gloabtemplate.Templates() {
patern := tpl.Name()
if !strings.HasPrefix(patern, "/") {
patern = "/" + patern
}
//處理首頁邏輯
tplname := tpl.Name()
if 0 == len(tplname) {
//模板名稱爲""那麼就默認首頁吧
//恰好/對應的模板名稱爲"",
tplname = serverCtx.Config.TemplateIndex
}
pageRouters = append(pageRouters, rest.Route{
Method: http.MethodGet,
Path: patern,
Handler: htmlhandler(gloabtemplate, tplname, serverCtx),
})
logx.Infof("register page %s %s", patern, tplname)
}
404等頁面
目前可以實現業務邏輯層面的404定製,如httpx.Error方法可用404.html替代。 對於部分場景如訪問一個不存在的url,則需要go-zero
官方提供支持,並開發接口。
集成
以上操作完成後,我們得到如下項目目錄,
tree /F /A
| go.mod
| go.sum
| html.api
| html.go
| readme.md
|
+---etc
| html-api.yaml
|
\---internal
+---config
| config.go
|
+---handler
| | funcs.go
| | html.go
| | routes.go
| |
| \---open
| authorizationhandler.go
|
+---logic
| \---open
| authorizationlogic.go
|
+---svc
| servicecontext.go
|
+---types
| types.go
|
\---view
+---public
| 404.html
| footer.html
| header.html
|
\---www
arg.html
embd.html
func.html
index.html
test.html
在routes.go
中添加如下代碼段即可
func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
engine.AddRoutes([]rest.Route{
{
Method: http.MethodPost,
Path: "/open/authorization",
Handler: open.AuthorizationHandler(serverCtx),
},
})
//添加這個代碼段
RegisterHtmlHandlers(engine, serverCtx)
}
本文代碼獲取
關注公衆號betaidea
輸入html
即可獲得html解析相關代碼 關注公衆號betaidea
輸入jwt
即可獲得gozero集成jwt-token相關代碼 關注公衆號betaidea
輸入gozero
即可gozero入門代碼
下一篇預告
目前貌似還沒找到go-zero對static file支持的例子,類似gin
哪樣做靜態資源服務貌的例子,那麼明天就寫一個吧。 在go-zero的路由框架下尋找解決方案。 《用go-zero 支持文件服務》
廣而告之
送福利了uniapp用戶福音來啦! 歷經數十萬用戶考驗,我們的客服系統終於對外提供服務了。 你還在爲商城接入客服煩惱嗎?只需一行代碼,即可接入啦!! 只需一行代碼!!!!
/*kefu.vue*/
<template>
<view>
<IdeaKefu :siteid="siteId" ></IdeaKefu>
</view>
</template>
<script>
import IdeaKefu from "@/components/idea-kefu/idea-kefu.vue"
export default {
components:{
IdeaKefu
},
data() {
return {
siteId:2
}
}
}
效果槓槓的