go?doudou應(yīng)用中使用注解示例詳解
快速上手
我們都知道go語(yǔ)言沒(méi)有原生的注解,但是做業(yè)務(wù)開(kāi)發(fā)有些時(shí)候沒(méi)有注解確實(shí)不方便。go-doudou通過(guò)go語(yǔ)言標(biāo)準(zhǔn)庫(kù)ast/parser
實(shí)現(xiàn)了對(duì)注解的支持。b站配套視頻教程地址:[golang] go-doudou微服務(wù)框架入門(mén)03-如何使用注解,如果喜歡看視頻,可直接跟視頻上手實(shí)踐。
我們通過(guò)一個(gè)簡(jiǎn)單的基于go-doudou開(kāi)發(fā)的服務(wù)來(lái)演示用法和效果。
準(zhǔn)備
- 本地安裝最新版go-doudou CLI
go install -v github.com/unionj-cloud/go-doudou@v1.1.9
本地安裝postman,用于測(cè)試接口:www.postman.com/
本地安裝goland
初始化工程
我們的服務(wù)名稱和模塊名稱都叫annotation
go-doudou svc init annotation
設(shè)計(jì)業(yè)務(wù)接口
go-doudou應(yīng)用的接口定義文件是項(xiàng)目根路徑下的svc.go
文件。打開(kāi)文件后按照如下代碼修改:
package service import "context" //go:generate go-doudou svc http --handler --doc type Annotation interface { // 此接口可公開(kāi)訪問(wèn),無(wú)需校驗(yàn)登錄和權(quán)限 GetGuest(ctx context.Context) (data string, err error) // 此接口只有登錄用戶有權(quán)訪問(wèn) // @role(USER,ADMIN) GetUser(ctx context.Context) (data string, err error) // 此接口只有管理員有權(quán)訪問(wèn) // @role(ADMIN) GetAdmin(ctx context.Context) (data string, err error) }
@role(USER,ADMIN)
和@role(ADMIN)
就是本文的主角。注解定義格式為:@注解名稱(參數(shù)1,參數(shù)2,參數(shù)3...)。可以根據(jù)業(yè)務(wù)實(shí)際需求,自定義各種不同的注解,@role
僅是一個(gè)例子,你還可以定義其他如@permission(create,update,del)
,以及無(wú)參數(shù)注解@inner()
。
生成代碼
點(diǎn)擊截圖中左上角的綠色三角形,執(zhí)行go:generate
指令,生成接口路由和http handler相關(guān)代碼,以及遵循OpenAPI 3.0規(guī)范的json文檔。
我們重點(diǎn)看一下transport/httpsrv/handler.go
文件。
package httpsrv import ( "net/http" ddmodel "github.com/unionj-cloud/go-doudou/framework/http/model" ) // http handler接口 type AnnotationHandler interface { GetGuest(w http.ResponseWriter, r *http.Request) GetUser(w http.ResponseWriter, r *http.Request) GetAdmin(w http.ResponseWriter, r *http.Request) } // 接口路由 func Routes(handler AnnotationHandler) []ddmodel.Route { return []ddmodel.Route{ { "GetGuest", "GET", "/guest", handler.GetGuest, }, { "GetUser", "GET", "/user", handler.GetUser, }, { "GetAdmin", "GET", "/admin", handler.GetAdmin, }, } } // 在內(nèi)存中存儲(chǔ)解析出來(lái)的注解信息 // ddmodel.AnnotationStore是map[string][]Annotation類(lèi)型的別名, // 鍵是路由名稱,值是注解結(jié)構(gòu)體切片。注解結(jié)構(gòu)體中存放了注解名稱和參數(shù)切片, // 下文我們實(shí)現(xiàn)的校驗(yàn)權(quán)限的中間件原理就是通過(guò)http.Request對(duì)象拿到路由名稱, // 然后用路由名稱從RouteAnnotationStore中找出存儲(chǔ)的注解結(jié)構(gòu)體切片, // 最后比對(duì)從內(nèi)存數(shù)據(jù)源或外部數(shù)據(jù)源拿到的用戶角色和注解結(jié)構(gòu)體的參數(shù)切片中的元素 // 判斷該用戶是否有權(quán)限繼續(xù)訪問(wèn)接口 var RouteAnnotationStore = ddmodel.AnnotationStore{ "GetUser": { { Name: "@role", Params: []string{ "USER", "ADMIN", }, }, }, "GetAdmin": { { Name: "@role", Params: []string{ "ADMIN", }, }, }, }
下載依賴
執(zhí)行命令go mod tidy
,下載項(xiàng)目依賴。此時(shí),服務(wù)已經(jīng)可以啟動(dòng)了,但是我們不急。下面我們要根據(jù)注解信息,編寫(xiě)中間件,實(shí)現(xiàn)我們依據(jù)用戶角色控制訪問(wèn)權(quán)限的需求。
Auth中間件
本示例項(xiàng)目的登錄憑證采用http basic的base64 token。我們打開(kāi)transport/httpsrv/middleware.go
文件,黏貼進(jìn)去如下代碼:
package httpsrv import ( "annotation/vo" "github.com/gorilla/mux" "github.com/unionj-cloud/go-doudou/toolkit/sliceutils" "net/http" ) // vo.UserStore是map[Auth]RoleEnum的別名類(lèi)型,鍵為用戶名和密碼構(gòu)成的結(jié)構(gòu)體,值為角色枚舉 // 我們用userStore代表數(shù)據(jù)庫(kù) func Auth(userStore vo.UserStore) func(inner http.Handler) http.Handler { return func(inner http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 從http.Request中拿到路由名稱 currentRoute := mux.CurrentRoute(r) if currentRoute == nil { inner.ServeHTTP(w, r) return } routeName := currentRoute.GetName() // 查詢?cè)撀酚墒欠裼嘘P(guān)聯(lián)的注解結(jié)構(gòu)體切片 // 如果沒(méi)有,則放行 if !RouteAnnotationStore.HasAnnotation(routeName, "@role") { inner.ServeHTTP(w, r) return } // 從請(qǐng)求頭中提取并解析http basic用戶名和密碼 user, pass, ok := r.BasicAuth() // 如果不成功,則禁止訪問(wèn),返回401 if !ok { w.Header().Set("WWW-Authenticate", `Basic realm="Provide user name and password"`) w.WriteHeader(401) w.Write([]byte("Unauthorised.\n")) return } // 從userStore中查詢是否存在此用戶 role, exists := userStore[vo.Auth{user, pass}] // 如果不存在,則禁止訪問(wèn),返回401 if !exists { w.Header().Set("WWW-Authenticate", `Basic realm="Provide user name and password"`) w.WriteHeader(401) w.Write([]byte("Unauthorised.\n")) return } // 如果存在,則判斷該接口是否允許該用戶所屬角色訪問(wèn) params := RouteAnnotationStore.GetParams(routeName, "@role") // 判斷該路由的@role注解的參數(shù)切片中是否包含該用戶的角色 // 如果不包含,則禁止訪問(wèn),返回403 if !sliceutils.StringContains(params, role.StringGetter()) { w.WriteHeader(403) w.Write([]byte("Access denied\n")) return } // 如果包含,則放行 inner.ServeHTTP(w, r) }) } }
至此,我們已經(jīng)完成核心邏輯開(kāi)發(fā)。最后我們只要把這個(gè)中間件加到go-doudou服務(wù)里即可。
修改main函數(shù)
package main import ( service "annotation" "annotation/config" "annotation/transport/httpsrv" "annotation/vo" ddhttp "github.com/unionj-cloud/go-doudou/framework/http" ) func main() { conf := config.LoadFromEnv() svc := service.NewAnnotation(conf) handler := httpsrv.NewAnnotationHandler(svc) srv := ddhttp.NewDefaultHttpSrv() // 將上文編寫(xiě)的Auth中間件加入go-doudou服務(wù)中 srv.AddMiddleware(httpsrv.Auth(vo.UserStore{ vo.Auth{ User: "guest", Pass: "guest", }: vo.GUEST, vo.Auth{ User: "user", Pass: "user", }: vo.USER, vo.Auth{ User: "admin", Pass: "admin", }: vo.ADMIN, })) srv.AddRoute(httpsrv.Routes(handler)...) srv.Run() }
啟動(dòng)服務(wù)
啟動(dòng)服務(wù)有多種方式:
go-doudou內(nèi)置啟動(dòng)命令(僅用于開(kāi)發(fā)階段):
go-doudou svc run
go run cmd/main.go
點(diǎn)擊main函數(shù)左邊的綠色 圖表
測(cè)試效果
將生成的annotation_openapi3.json
文件導(dǎo)入postman中即可測(cè)試。postman的用法超出了本文的范疇,此處只附上部分截圖供參考。
注解實(shí)現(xiàn)原理
go-doudou實(shí)現(xiàn)注解的原理非常簡(jiǎn)單,就是通過(guò)go語(yǔ)言標(biāo)準(zhǔn)庫(kù)ast/parser
對(duì)接口定義文件svc.go
文件中的源碼進(jìn)行解析,將注釋塊里的注解通過(guò)正則表達(dá)式提取出來(lái),創(chuàng)建Annotation
結(jié)構(gòu)體實(shí)例,關(guān)聯(lián)到對(duì)應(yīng)的接口上,最后作為靜態(tài)變量RouteAnnotationStore
的值通過(guò)代碼生成器輸出到transport/httpsrv/handler.go
文件里的,供開(kāi)發(fā)者調(diào)用。
以下是提取注解的源碼,供參考:
var reAnno = regexp.MustCompile(`@(\S+?)\((.*?)\)`) func GetAnnotations(text string) []Annotation { if !reAnno.MatchString(text) { return nil } var annotations []Annotation matches := reAnno.FindAllStringSubmatch(text, -1) for _, item := range matches { name := fmt.Sprintf(`@%s`, item[1]) var params []string if stringutils.IsNotEmpty(item[2]) { params = strings.Split(strings.TrimSpace(item[2]), ",") } annotations = append(annotations, Annotation{ Name: name, Params: params, }) } return annotations }
總結(jié)
本文通過(guò)一個(gè)快速上手實(shí)例,講解了go-doudou注解特性的用法和原理。有任何疑問(wèn)都可以在下方留言。示例源碼地址:github.com/unionj-clou…。
關(guān)于go-doudou的更多特性和用法請(qǐng)參考官方文檔:go-doudou.unionj.cloud/
以上就是go doudou應(yīng)用中使用注解示例詳解的詳細(xì)內(nèi)容,更多關(guān)于go doudou應(yīng)用注解的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- go?doudou應(yīng)用中使用枚舉類(lèi)型教程示例
- go?doudou開(kāi)發(fā)gRPC服務(wù)快速上手實(shí)現(xiàn)詳解
- Golang?gRPC?HTTP協(xié)議轉(zhuǎn)換示例
- Go Grpc Gateway兼容HTTP協(xié)議文檔自動(dòng)生成網(wǎng)關(guān)
- Go?gRPC進(jìn)階教程gRPC轉(zhuǎn)換HTTP
- Go?gRPC服務(wù)proto數(shù)據(jù)驗(yàn)證進(jìn)階教程
- Go?gRPC服務(wù)進(jìn)階middleware使用教程
- go?doudou開(kāi)發(fā)單體RESTful服務(wù)快速上手教程
相關(guān)文章
Go語(yǔ)言獲取系統(tǒng)性能數(shù)據(jù)gopsutil庫(kù)的操作
這篇文章主要介紹了Go語(yǔ)言獲取系統(tǒng)性能數(shù)據(jù)gopsutil庫(kù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12GoFrame框架Scan類(lèi)型轉(zhuǎn)換實(shí)例
這篇文章主要為大家介紹了GoFrame框架Scan類(lèi)型轉(zhuǎn)換的實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06golang實(shí)現(xiàn)并發(fā)數(shù)控制的方法
下面小編就為大家分享一篇golang實(shí)現(xiàn)并發(fā)數(shù)控制的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12golang程序進(jìn)度條實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了golang程序?qū)崿F(xiàn)進(jìn)度條示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08golang基礎(chǔ)之字符串與int、int64類(lèi)型互相轉(zhuǎn)換
這篇文章主要給大家介紹了關(guān)于golang基礎(chǔ)之字符串與int、int64類(lèi)型互相轉(zhuǎn)換的相關(guān)資料,在Go語(yǔ)言中string轉(zhuǎn)int是一項(xiàng)常見(jiàn)的操作,需要的朋友可以參考下2023-07-07go語(yǔ)言實(shí)現(xiàn)字符串base64編碼的方法
這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)字符串base64編碼的方法,實(shí)例分析了Go語(yǔ)言操作字符串的技巧及base64編碼的使用技巧,需要的朋友可以參考下2015-03-03golang 的string與[]byte轉(zhuǎn)換方式
這篇文章主要介紹了golang 的string與[]byte轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Go語(yǔ)言實(shí)現(xiàn)基于websocket瀏覽器通知功能
這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)基于websocket瀏覽器通知功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07