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

winlion · · 1019 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

# 扩展go-zero,使之支持html模板解析自动化 go-zero本身支持html模板解析,我们只需要添加url对应模板解hanlder,实现逻辑就可以了 # 但是winlion太懒了,我甚至想 + 不写任何一个和模板相关的handler + 如果有新的模板,直接把模板到某个特定目录就好,不要动任何go代码 + 在开发环境下没有缓存,修改了模板文件无需重启 需求在这里,开撸吧 # 在代码开始前,你可能需要阅读 [金光灿灿的Gorm V2+适合创业的golang微服务框架go-zero实战](https://mp.weixin.qq.com/s/NQMDvxvE1kH6MrpW50SUJg) 如果对go-zero已经了解,直接跳过吧 # 创建项目 ## 生成go.mod文件 以如下指令创建项目 ```bash mkdir html cd html go mod init html ``` ## 定义html.api 本文设计API如下 |描述|格式|方法|参数|返回|是否需要鉴权| |----|----|----|----|----|----| |用户登录|/open/authorization|post|mobile:手机号,passwd:密码,code:图片验证码|id:用户ID,token:用户token|否| 根据以上描述,书写api的模板文件如下 ```golang 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生成 ## 生成代码 采用如下指令生成代码 ```bash goctl api go -api html.api -dir . ``` 此时用`go run html.go`指令可以发现系统以及运行 # html模板自动解析实现思路 模板解析需要了解如下俩个已知知识点 + html网页输出本质上是get请求输出 + 相对于一个项目来说,模板文件个数是有限的,因此我们可以将模板枚举出来,完成访模板名称和请求之间的映射 对于第一个,我们可以构建get路由来实现请求,以首页请求`http://127.0.0.1:8888/index.html`为例,核心代码如下, ```golang htmltplrouter:= rest.Route{ Method: http.MethodGet, Path: "/index.html", Handler: htmlhandler(...), } engine.AddRoute(htmltplrouter) ``` 在上述代码中,`htmlhandler`函数实现了对请求的响应,也就是解析了模板并将模板内容输出 ```golang //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`文件内容如下 ```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`如下 ```bash 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之间的对应关系,核心代码如下 ```golang 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. 定义相关函数 ```golang //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())`来注入响应函数 2. 定义模板文件 新建文件`view/www/version.html`,内容如下 ```html {{define "www/version.html"}} <h1>当前版本号:{{version}}</h1> <h1>这里测试带参数的函数:{{hello "word"}}</h1> {{end}} ``` 3. 无参数的函数展示 此时模板文件中通过 `{{version}}` 即可调用并显示版本号0.01 4. 有参数的函数 对应有参数的函数,按照参数顺序排列,中间用空格隔开 5. 以上显示结果 ```html 当前版本号:0.01 这里测试带参数的函数:hello word ``` ## 如何模板嵌套 使用`templete`指令进行嵌套 新建`view/public/header.html`内容如下 ```html <!-- 顶部菜单 Start --> <div class="top-menu-wrapper index-menu"> <h1>这是Head</h1> </div> ``` 新建`view/public/footer.html`内容如下 ```html <!-- 顶部菜单 Start --> <div class="top-menu-wrapper index-menu"> <h1>这是footer</h1> </div> ``` 新建`view/www/index.html`文件,内容如下 ```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了 ```golang data := r.URI.Query err := t.ExecuteTemplate(w, tplname, data) ``` 新建`view/www/arg.html`文件 ```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`文件 ```html {{define "www/embd.html"}} 没加点:{{template "www/arg.html"}} ======= 加点:{{template "www/arg.html" .}} {{end}} ``` 结果如下 ```bash 没加点: <h5>arga=</h5> <h5>argb=</h5> ======= 加点: <h5>arga=[123]</h5> <h5>argb=[456]</h5> ``` ## 如何实现模板热更新 假设我们的应用支持开发模式和生产模式,在生产模式下,由于有性能考虑,系统不需要每次访问都解析模板。而在开发模式下,每个模板有所任何小的修改,我们都希望模板能自动更新,怎么实现这个功能呢? 方案很多,有文件监听方案,如`github.com/fsnotify/fsnotify`监听模板目录,也有标记位方案,无论模板有没有变动,只要是开发模式,每次请求都重新加载模板并解析,`gin`就是这种方案,本文也采用这种方案,核心代码如下 ```golang //模板名字就是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()) ``` ## 如何设置首页 本质上是指定`/`请求对应的模板,以及系统错误对应的模板 ```golang 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`官方提供支持,并开发接口。 ## 集成 以上操作完成后,我们得到如下项目目录, ```bash 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`中添加如下代码段即可 ```golang 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用户福音来啦! 历经数十万用户考验,我们的客服系统终于对外提供服务了。 你还在为商城接入客服烦恼吗?只需一行代码,即可接入啦!! 只需一行代码!!!! ```html /*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 } } } ``` 效果杠杠的 ![客服效果](http://kefu.techidea8.com/html/wiki/assets/image/vistor-1.png) 开发文档地址 [http://kefu.techidea8.com/html/wiki/](http://kefu.techidea8.com/html/wiki/)

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1019 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传