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