golang微服務(wù)框架go-zero系列-3:在go-zero中使用jwt-token鑒權(quán)實踐

go-zero本身支持html模板解析,我們只需要添加url對應(yīng)模板解hanlder,實現(xiàn)邏輯就可以了

但是winlion太懶了,我甚至想

  • 不寫任何一個和模板相關(guān)的handler
  • 如果有新的模板,直接把模板到某個特定目錄就好,不要動任何go代碼
  • 在開發(fā)環(huán)境下沒有緩存,修改了模板文件無需重啟

需求在這里,開擼吧

在代碼開始前,你可能需要閱讀

金光燦燦的Gorm V2+適合創(chuàng)業(yè)的golang微服務(wù)框架go-zero實戰(zhàn)
如果對go-zero已經(jīng)了解,直接跳過吧

創(chuàng)建項目

生成go.mod文件

以如下指令創(chuàng)建項目

mkdir html
cd html
go mod init  html

定義html.api

本文設(shè)計API如下

描述 格式 方法 參數(shù) 返回 是否需要鑒權(quán)
用戶登錄 /open/authorization post mobile:手機號,passwd:密碼,code:圖片驗證碼 id:用戶ID,token:用戶token

根據(jù)以上描述,書寫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模板相關(guān),可以不適用goctl工具
  • 但是由于使用工具可以為我們節(jié)省很多搭建框架相關(guān)的工作,所以建議使用用ctl生成

生成代碼

采用如下指令生成代碼

goctl api  go   -api   html.api   -dir  .

此時用go run html.go指令可以發(fā)現(xiàn)系統(tǒng)以及運行

html模板自動解析實現(xiàn)思路

模板解析需要了解如下倆個已知知識點

  • html網(wǎng)頁輸出本質(zhì)上是get請求輸出
  • 相對于一個項目來說,模板文件個數(shù)是有限的,因此我們可以將模板枚舉出來,完成訪模板名稱和請求之間的映射

對于第一個,我們可以構(gòu)建get路由來實現(xiàn)請求,以首頁請求http://127.0.0.1:8888/index.html為例,核心代碼如下,

    htmltplrouter:= rest.Route{
            Method:  http.MethodGet,
            Path:    "/index.html",
            Handler: htmlhandler(...),
    }

    engine.AddRoute(htmltplrouter)

在上述代碼中,htmlhandler函數(shù)實現(xiàn)了對請求的響應(yīng),也就是解析了模板并將模板內(nèi)容輸出

//gloabtemplate:全局解析的模板參數(shù)
//tplname:模板名稱,
//serverCtx 應(yīng)用配置
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
        //如果是調(diào)試模式,則支持熱解析
        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和模板名稱之間的映射關(guān)系

這里有幾個點需要強調(diào):

  • 在golang中,每個包含模板內(nèi)容的html文件會被解析成一個模板,如在view/www/下新建test.html文件,即使里面沒有內(nèi)容,系統(tǒng)也會將其解析得到一個名叫test.html的模板强经。
  • 如果在模板文件以template標簽中定義名稱為www/test.html的模板,則系統(tǒng)又會解析得到一個名叫www/test.html的模板,此時存在倆個模板,一個名叫test.html,一個名叫www/test.html

view/www/test.html文件內(nèi)容如下

{{define "www/test.html"}}
<h1>這是模板www/test.html的內(nèi)容</h1>
{{end}}

因此我們可以取巧,將模板名稱命名成需要建立映射關(guān)系的uri
比如外部通過http://127.0.0.1:8888/www/test.html來訪問,此時req.URI.path為/www/test.html 我們可以用這個作為模板名稱

如何枚舉模板

這里用到了ParseGlob函數(shù),這個函數(shù)本質(zhì)上是對filepath.ParseGlob()template.ParseFiles()的封裝,可以遍歷滿足一定格式的路徑的所有文件,假設(shè)我們建立模板存放目錄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之間的對應(yīng)關(guān)系,核心代碼如下

  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)

如何在模板中使用函數(shù)

有時候我們需要在模板中使用函數(shù),則需要用到函數(shù)映射功能,golang提供接口函數(shù)Funcs()來注入,

假設(shè)我們需要在/www/version.html中查看系統(tǒng)版本,應(yīng)該怎么做呢?

  1. 定義相關(guān)函數(shù)
//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 {
    //這個函數(shù)返回當前版本號0.0.1
    return "0.01"

}
func hello(str string) string {
    //這個函數(shù)返回當前版本號0.0.1
    return "hello "+ str

}

應(yīng)用可以通過 template.New("").Funcs(FuncMap())來注入響應(yīng)函數(shù)

  1. 定義模板文件
    新建文件view/www/version.html,內(nèi)容如下
{{define "www/version.html"}}
<h1>當前版本號:{{version}}</h1>
<h1>這里測試帶參數(shù)的函數(shù):{{hello "word"}}</h1>
{{end}}
  1. 無參數(shù)的函數(shù)展示
    此時模板文件中通過 {{version}} 即可調(diào)用并顯示版本號0.01

  2. 有參數(shù)的函數(shù)
    對應(yīng)有參數(shù)的函數(shù),按照參數(shù)順序排列,中間用空格隔開

  3. 以上顯示結(jié)果

當前版本號:0.01
這里測試帶參數(shù)的函數(shù):hello word

如何模板嵌套

使用templete指令進行嵌套

新建view/public/header.html內(nèi)容如下

<!-- 頂部菜單 Start -->
<div class="top-menu-wrapper index-menu">
    <h1>這是Head</h1>
</div>

新建view/public/footer.html內(nèi)容如下

<!-- 頂部菜單 Start -->
<div class="top-menu-wrapper index-menu">
    <h1>這是footer</h1>
</div>

新建view/www/index.html文件,內(nèi)容如下

<!DOCTYPE html>
<html>
<head></head>
<body>
{{template "header.html" .}}
<div class="content-box" data-spy="scroll" data-target=".section-scrollspy"> 
  <h1>這是Index的內(nèi)容</h1> 
</div> 
{{template "footer.html" .}}
</body>
</html>

此時編譯后即可得到如下內(nèi)容

這是Head
這是Index的內(nèi)容
這是footer

如何在模板中使用變量

  • 在模板中直接使用
    首先需要將變量暴露到模板中,這里我們使用到了ExecuteTemplate函數(shù),該函數(shù)第三個參數(shù)即可以在模板里面訪問的參數(shù),比如如下代碼,則在模板中可以訪問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

系統(tǒng)返回結(jié)果

arga=[123]
argb=[456]
  • 在嵌套模板中使用

在嵌套模板中使用需要將對象傳入,方式是在模板名后加一個.性湿,如下
新建view/www/embd.html文件

{{define "www/embd.html"}}
沒加點:{{template "www/arg.html"}}
=======
加點:{{template "www/arg.html" .}}
{{end}}

結(jié)果如下

沒加點:
<h5>arga=</h5>
<h5>argb=</h5>
=======
加點:
<h5>arga=[123]</h5>
<h5>argb=[456]</h5>


如何實現(xiàn)模板熱更新

假設(shè)我們的應(yīng)用支持開發(fā)模式和生產(chǎn)模式,在生產(chǎn)模式下,由于有性能考慮,系統(tǒng)不需要每次訪問都解析模板们拙。而在開發(fā)模式下,每個模板有所任何小的修改较鼓,我們都希望模板能自動更新,怎么實現(xiàn)這個功能呢?
方案很多,有文件監(jiān)聽方案,如github.com/fsnotify/fsnotify監(jiān)聽模板目錄,也有標記位方案,無論模板有沒有變動,只要是開發(fā)模式,每次請求都重新加載模板并解析,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())

如何設(shè)置首頁

本質(zhì)上是指定/請求對應(yīng)的模板,以及系統(tǒng)錯誤對應(yīng)的模板

for _, tpl := range gloabtemplate.Templates() {
        patern := tpl.Name()
        if !strings.HasPrefix(patern, "/") {
            patern = "/" + patern
        }

        //處理首頁邏輯
        tplname := tpl.Name()
        if 0 == len(tplname) {
            //模板名稱為""那么就默認首頁吧
            //恰好/對應(yīng)的模板名稱為"",
            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等頁面

目前可以實現(xiàn)業(yè)務(wù)邏輯層面的404定制,如httpx.Error方法可用404.html替代。
對于部分場景如訪問一個不存在的url,則需要go-zero官方提供支持,并開發(fā)接口悉罕。

集成

以上操作完成后,我們得到如下項目目錄,

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)
}

本文代碼獲取

關(guān)注公眾號betaidea 輸入html即可獲得html解析相關(guān)代碼
關(guān)注公眾號betaidea 輸入jwt即可獲得gozero集成jwt-token相關(guān)代碼
關(guān)注公眾號betaidea 輸入gozero即可gozero入門代碼

下一篇預(yù)告

目前貌似還沒找到go-zero對static file支持的例子,類似gin哪樣做靜態(tài)資源服務(wù)貌的例子,那么明天就寫一個吧璧南。
在go-zero的路由框架下尋找解決方案掌逛。
《用go-zero 支持文件服務(wù)》

廣而告之

送福利了uniapp用戶福音來啦!
歷經(jīng)數(shù)十萬用戶考驗,我們的客服系統(tǒng)終于對外提供服務(wù)了司倚。
你還在為商城接入客服煩惱嗎?只需一行代碼,即可接入啦!!
只需一行代碼!!!!

/*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
            }
        }
    }   

效果杠杠的


客服效果

開發(fā)文檔地址
http://kefu.techidea8.com/html/wiki/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末豆混,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子动知,更是在濱河造成了極大的恐慌皿伺,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盒粮,死亡現(xiàn)場離奇詭異鸵鸥,居然都是意外死亡,警方通過查閱死者的電腦和手機丹皱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門妒穴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人摊崭,你說我怎么就攤上這事讼油。” “怎么了爽室?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵汁讼,是天一觀的道長。 經(jīng)常有香客問我阔墩,道長嘿架,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任啸箫,我火速辦了婚禮耸彪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忘苛。我一直安慰自己蝉娜,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布扎唾。 她就那樣靜靜地躺著召川,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胸遇。 梳的紋絲不亂的頭發(fā)上荧呐,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音,去河邊找鬼倍阐。 笑死概疆,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的峰搪。 我是一名探鬼主播岔冀,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼概耻!你這毒婦竟也來了使套?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤咐蚯,失蹤者是張志新(化名)和其女友劉穎童漩,沒想到半個月后弄贿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體春锋,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年差凹,在試婚紗的時候發(fā)現(xiàn)自己被綠了期奔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡危尿,死狀恐怖呐萌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谊娇,我是刑警寧澤肺孤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站济欢,受9級特大地震影響赠堵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜法褥,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一茫叭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧半等,春花似錦揍愁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至切距,卻和暖如春朽缎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工饵沧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锨络,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓狼牺,卻偏偏與公主長得像羡儿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子是钥,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353