go?doudou應用中使用注解示例詳解
快速上手
我們都知道go語言沒有原生的注解,但是做業(yè)務開發(fā)有些時候沒有注解確實不方便。go-doudou通過go語言標準庫ast/parser實現(xiàn)了對注解的支持。b站配套視頻教程地址:[golang] go-doudou微服務框架入門03-如何使用注解,如果喜歡看視頻,可直接跟視頻上手實踐。
我們通過一個簡單的基于go-doudou開發(fā)的服務來演示用法和效果。
準備
- 本地安裝最新版go-doudou CLI
go install -v github.com/unionj-cloud/go-doudou@v1.1.9
本地安裝postman,用于測試接口:www.postman.com/
本地安裝goland
初始化工程
我們的服務名稱和模塊名稱都叫annotation
go-doudou svc init annotation
設計業(yè)務接口
go-doudou應用的接口定義文件是項目根路徑下的svc.go文件。打開文件后按照如下代碼修改:
package service
import "context"
//go:generate go-doudou svc http --handler --doc
type Annotation interface {
// 此接口可公開訪問,無需校驗登錄和權限
GetGuest(ctx context.Context) (data string, err error)
// 此接口只有登錄用戶有權訪問
// @role(USER,ADMIN)
GetUser(ctx context.Context) (data string, err error)
// 此接口只有管理員有權訪問
// @role(ADMIN)
GetAdmin(ctx context.Context) (data string, err error)
}
@role(USER,ADMIN)和@role(ADMIN)就是本文的主角。注解定義格式為:@注解名稱(參數(shù)1,參數(shù)2,參數(shù)3...)??梢愿鶕?jù)業(yè)務實際需求,自定義各種不同的注解,@role僅是一個例子,你還可以定義其他如@permission(create,update,del),以及無參數(shù)注解@inner()。
生成代碼
點擊截圖中左上角的綠色三角形,執(zhí)行go:generate指令,生成接口路由和http handler相關代碼,以及遵循OpenAPI 3.0規(guī)范的json文檔。

我們重點看一下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類型的別名,
// 鍵是路由名稱,值是注解結構體切片。注解結構體中存放了注解名稱和參數(shù)切片,
// 下文我們實現(xiàn)的校驗權限的中間件原理就是通過http.Request對象拿到路由名稱,
// 然后用路由名稱從RouteAnnotationStore中找出存儲的注解結構體切片,
// 最后比對從內(nèi)存數(shù)據(jù)源或外部數(shù)據(jù)源拿到的用戶角色和注解結構體的參數(shù)切片中的元素
// 判斷該用戶是否有權限繼續(xù)訪問接口
var RouteAnnotationStore = ddmodel.AnnotationStore{
"GetUser": {
{
Name: "@role",
Params: []string{
"USER",
"ADMIN",
},
},
},
"GetAdmin": {
{
Name: "@role",
Params: []string{
"ADMIN",
},
},
},
}
下載依賴
執(zhí)行命令go mod tidy,下載項目依賴。此時,服務已經(jīng)可以啟動了,但是我們不急。下面我們要根據(jù)注解信息,編寫中間件,實現(xiàn)我們依據(jù)用戶角色控制訪問權限的需求。
Auth中間件
本示例項目的登錄憑證采用http basic的base64 token。我們打開transport/httpsrv/middleware.go文件,黏貼進去如下代碼:
package httpsrv
import (
"annotation/vo"
"github.com/gorilla/mux"
"github.com/unionj-cloud/go-doudou/toolkit/sliceutils"
"net/http"
)
// vo.UserStore是map[Auth]RoleEnum的別名類型,鍵為用戶名和密碼構成的結構體,值為角色枚舉
// 我們用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()
// 查詢該路由是否有關聯(lián)的注解結構體切片
// 如果沒有,則放行
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服務里即可。
修改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服務中
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()
}
啟動服務
啟動服務有多種方式:
go-doudou內(nèi)置啟動命令(僅用于開發(fā)階段):
go-doudou svc run
go run cmd/main.go
點擊main函數(shù)左邊的綠色 圖表

測試效果
將生成的annotation_openapi3.json文件導入postman中即可測試。postman的用法超出了本文的范疇,此處只附上部分截圖供參考。

注解實現(xiàn)原理
go-doudou實現(xiàn)注解的原理非常簡單,就是通過go語言標準庫ast/parser對接口定義文件svc.go文件中的源碼進行解析,將注釋塊里的注解通過正則表達式提取出來,創(chuàng)建Annotation結構體實例,關聯(lián)到對應的接口上,最后作為靜態(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
}
總結
本文通過一個快速上手實例,講解了go-doudou注解特性的用法和原理。有任何疑問都可以在下方留言。示例源碼地址:github.com/unionj-clou…。
關于go-doudou的更多特性和用法請參考官方文檔:go-doudou.unionj.cloud/
以上就是go doudou應用中使用注解示例詳解的詳細內(nèi)容,更多關于go doudou應用注解的資料請關注腳本之家其它相關文章!
相關文章
golang實現(xiàn)http服務器處理靜態(tài)文件示例
這篇文章主要介紹了golang實現(xiàn)http服務器處理靜態(tài)文件的方法,涉及Go語言基于http協(xié)議處理文件的相關技巧,需要的朋友可以參考下2016-07-07

