手记

golang微服务框架go-zero系列-3:扩展go-zero,使之支持html模板解析自动化

扩展go-zero,使之支持html模板解析自动化

go-zero本身支持html模板解析,我们只需要添加url对应模板解hanlder,实现逻辑就可以了

但是winlion太懒了,我甚至想

  • 不写任何一个和模板相关的handler

  • 如果有新的模板,直接把模板到某个特定目录就好,不要动任何go代码

  • 在开发环境下没有缓存,修改了模板文件无需重启

需求在这里,开撸吧

创建项目

生成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中查看系统版本,应该怎么做呢?

  1. 定义相关函数

//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())来注入响应函数

  1. 定义模板文件

新建文件view/www/version.html,内容如下


{{define "www/version.html"}}

<h1>当前版本号:{{version}}</h1>

<h1>这里测试带参数的函数:{{hello "word"}}</h1>

{{end}}

  1. 无参数的函数展示

此时模板文件中通过 {{version}} 即可调用并显示版本号0.01

  1. 有参数的函数

对应有参数的函数,按照参数顺序排列,中间用空格隔开

  1. 以上显示结果

当前版本号: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)

}

下一篇预告

目前貌似还没找到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

}

}

}

效果杠杠的

开发文档地址

1人推荐
随时随地看视频
慕课网APP