golang微服务框架go-zero系列-3:在go-zero中使用jwt-token鉴权实践

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

  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

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

  3. 以上显示结果

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

效果杠杠的


客服效果

开发文档地址
http://kefu.techidea8.com/html/wiki/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345