Go基于struct?tag實(shí)現(xiàn)結(jié)構(gòu)體字段級(jí)別的訪問控制
struct tag 是什么?
在Go 中,結(jié)構(gòu)體主要是用于定義復(fù)雜數(shù)據(jù)類型,而 struct tag 則是附加在 struct 字段后的字符串,提供了一種方式來存儲(chǔ)關(guān)于字段的元信息,然后,tag 在程序運(yùn)行時(shí)一般不會(huì)直接影響程序邏輯,
如下是一個(gè)定義了 tag 的結(jié)構(gòu)體 Person 類型。
type Person struct { Name string `json:"name"` Age int `json:"age"` }
例子中,json:"name"
和json:"age"
就是結(jié)構(gòu)體 tag。結(jié)構(gòu)體 tag 的使用非常直觀。你只需要在定義結(jié)構(gòu)體字段后,通過反引號(hào) `` 包裹起來的鍵值對(duì)形式就可定義它們。
具體有什么用呢?
這個(gè) tag 究竟有什么用呢?為何要定義它們。
單從這個(gè)例子中來看,假設(shè)你是在 "encoding/json" 庫中使用 Person
結(jié)構(gòu)體,它是告訴 Go 在處理 JSON 序列化和反序列化時(shí),字段名稱的轉(zhuǎn)化規(guī)則。
讓我們通過它在 "encoding/json" 的使用說明它的效果吧。
p := Person{Name: "John", Age: 30} jsonData, err := json.Marshal(p) if err != nil { log.Println(err) } fmt.Println(string(jsonData))
輸出:
{"name":"John","age":30}
可以看到輸出的 JSON 的 key
是 name 和 age,而非 Name 和 Age。
與其他語言對(duì)比的話,雖然 Go 的 struct tag 在某種程度上類似于 Java 的注解或 C# 的屬性,但 Go 的 tag 更加簡(jiǎn)潔,并且主要通過反射機(jī)制在運(yùn)行時(shí)被訪問。
這種設(shè)計(jì)反映了Go語言的哲學(xué):簡(jiǎn)單、直接而有效。但確實(shí)也是功能有限!
常見使用場(chǎng)景
結(jié)構(gòu)體 tag 在 Go 語言中常見用途,我平時(shí)最常見有如下這些。
JSON/XML 序列反序列化
如前面的介紹的案例中,通過 encoding/json
或者其他的庫如 encoding/xml
庫,tag 可以控制如何將結(jié)構(gòu)體字段轉(zhuǎn)換為 JSON 或 XML,或者如何從它們轉(zhuǎn)換回來。
數(shù)據(jù)庫操作
在ORM(對(duì)象關(guān)系映射)庫中,tag 可以定義數(shù)據(jù)庫表的列名、類型或其他特性。
如我們?cè)谑褂?Gorm 時(shí),會(huì)看到這樣的定義:
type User struct { gorm.Model Name string `gorm:"type:varchar(100);unique_index"` Age int `gorm:"index:age"` Active bool `gorm:"default:true"` }
結(jié)構(gòu)體 tag 可用于定義數(shù)據(jù)庫表的列名、類型或其他特性。
數(shù)據(jù)驗(yàn)證
在一些庫中,tag 用于驗(yàn)證數(shù)據(jù),例如,確保一個(gè)字段是有效的電子郵件地址。
如下是 govalidator[1] 使用結(jié)構(gòu)體上 tag 實(shí)現(xiàn)定義數(shù)據(jù)驗(yàn)證規(guī)則的一個(gè)案例。
type User struct { Email string `valid:"email"` Age int `valid:"range(18|99)"` }
在這個(gè)例子中,valid tag 定義了字段的驗(yàn)證規(guī)則,如 email
字段值是否是有效的 email
,age
字段是否滿足數(shù)值在 18 到 99 之間等。
我們只要將類型為 User
類型的變量交給 govalidator
,它可以根據(jù)這些規(guī)則來驗(yàn)證數(shù)據(jù),確保數(shù)據(jù)的正確性和有效性。
示例如下:
valid, err := govalidator.ValidateStruct(User{Email: "test@example.com", Age: 20})
返回的 valid:
為 true
或 false
,如果發(fā)生錯(cuò)誤,err
提供具體的錯(cuò)誤原因。
tag 行為自定義
前面展示的都是利用標(biāo)準(zhǔn)庫或三方庫提供的能力,如果想自定義 tag 該如何實(shí)現(xiàn)?畢竟有些情況下,如果默認(rèn)提供的 tag 提供的能力不滿足需求,我們還是希望可以自定義 tag 的行為。
這需要了解與理解 Go 的反射機(jī)制,它為數(shù)據(jù)處理和元信息管理提供了強(qiáng)大的靈活性。
如下的示例代碼:
type Person struct { Name string `mytag:"MyName"` } t := reflect.TypeOf(Person{}) field, _ := t.FieldByName("Name") fmt.Println(field.Tag.Get("mytag")) // 輸出: MyName
在這個(gè)例子中,我們的 Person
的字段 Name
有一個(gè)自定義的 tag - mytag
,我們直接通過反射就可以訪問它。
這只是簡(jiǎn)單的演示如何訪問到 tag。如何使用它呢?
這就要基于實(shí)際的場(chǎng)景了,當(dāng)然,這通常也離不開與反射配合。下面我們來通過一個(gè)實(shí)際的例子介紹。
案例:結(jié)構(gòu)體字段訪問控制
讓我們考慮一個(gè)實(shí)際的場(chǎng)景:一個(gè)結(jié)構(gòu)訪問控制系統(tǒng)。
這個(gè)系統(tǒng)中,我們可以根據(jù)用戶的角色(如 admin、user)或者請(qǐng)求的來源(admin、web)控制對(duì)結(jié)構(gòu)體字段的訪問。具體而言,假設(shè)我定義了一個(gè)包含敏感信息的結(jié)構(gòu)體,我可以使用自定義 tag 來標(biāo)記每個(gè)字段的訪問權(quán)限。
是不是想到,這或許可用在 API 接口范圍字段的控制上,防止泄露敏感數(shù)據(jù)給用戶。
接下來,具體看看如何做吧?
定義結(jié)構(gòu)體
我們首先定義一個(gè)UserProfile
結(jié)構(gòu)體,其中包含用戶的各種信息。每個(gè)信息字段都有一個(gè)自定義的 access
tag,用于標(biāo)識(shí)字段訪問權(quán)限(admin
或user
)。
type UserProfile struct { Username string `access:"user"` // 所有用戶可見 Email string `access:"user"` // 所有用戶可見 PhoneNumber string `access:"admin"` // 僅管理員可見 Address string `access:"admin"` // 僅管理員可見 }
其中,PhoneNumber
和 Address
是敏感字段,它只對(duì) admin 角色可見。而 UserName
和 Email
則是所有用戶可見。
到此,結(jié)構(gòu)體 UserProfile
定義完成。
實(shí)現(xiàn)權(quán)限控制
接下來就是要實(shí)現(xiàn)一個(gè)函數(shù),實(shí)現(xiàn)根據(jù) UserProfile
定義的 access
tag 決定字段內(nèi)容的可見性。
假設(shè)函數(shù)名稱為 FilterFieldsByRole
,它接受一個(gè) UserProfile
類型變量和用戶角色,返回內(nèi)容一個(gè)過濾后的 map
(由 fieldname 到 fieldvalue 組成的映射),其中只包含角色有權(quán)訪問的字段。
func FilterFieldsByRole(profile UserProfile, role string) map[string]string { result := make(map[string]string) val := reflect.ValueOf(profile) typ := val.Type() for i := 0; i < val.NumField(); i++ { field := typ.Field(i) accessTag := field.Tag.Get("access") if accessTag == "user" || accessTag == role { // 獲取字段名稱 fieldName := strings.ToLower(field.Name) // 獲取字段值 fieldValue := val.Field(i).String() // 組織返回結(jié)果 result result[fieldName] = fieldValue } } return result }
權(quán)限控制的重點(diǎn)邏輯部分,就是 if accessTag == "user" || accessTag == role
這段判斷條件。當(dāng)滿足條件之后,接下來要做的就是通過反射獲取字段名稱和值,并組織目標(biāo)的 Map 類變量 result
了。
使用演示
讓我們來使用下 FilterFieldsByRole
函數(shù),檢查下是否滿足按角色訪問特定的用戶信息的功能。
示例代碼如下:
func main() { profile := UserProfile{ Username: "johndoe", Email: "johndoe@example.com", PhoneNumber: "123-456-7890", Address: "123 Elm St", } // 假設(shè)當(dāng)前用戶是普通用戶 userInfo := FilterFieldsByRole(profile, "user") fmt.Println(userInfo) // 假設(shè)當(dāng)前用戶是管理員 adminInfo := FilterFieldsByRole(profile, "admin") fmt.Println(adminInfo) }
輸出:
map[username:johndoe email:johndoe@example.com]
map[username:johndoe email:johndoe@example.com phonenumber:123-456-7890 address:123 Elm St]
這個(gè)場(chǎng)景,通過自定義結(jié)構(gòu)體 tag,給予指定角色,很輕松地就實(shí)現(xiàn)了一個(gè)基于角色的權(quán)限控制。
毫無疑問,這個(gè)代碼更加清晰和可維護(hù),而且具有極大靈活性、擴(kuò)展性。如果想擴(kuò)展更多角色,也是更加容易。
不過還是要說明下,如果在 API 層面使用這樣的能力,還是要考慮反射可能帶來的性能影響。
總結(jié)
這篇博文介紹了Go語言中結(jié)構(gòu)體 tag 的基礎(chǔ)知識(shí),如是什么,如何使用。另外,還介紹了它們?cè)诓煌瑘?chǎng)景下的應(yīng)用。通過簡(jiǎn)單的例子和對(duì)比,我們看到了 Go 中結(jié)構(gòu)體 tag 的作用。
文章的最后,通過一個(gè)實(shí)際案例,演示了如何使用 struct tag 使我們代碼更加靈活強(qiáng)大。雖然 struct tag 的使用非常直觀,但正確地利用這些 tag 可以極大提升我們程序的功能和效率
引用鏈接
[1] govalidator: github.com/asaskevich/govalidator
以上就是Go基于struct tag實(shí)現(xiàn)結(jié)構(gòu)體字段級(jí)別的訪問控制的詳細(xì)內(nèi)容,更多關(guān)于Go struct tag訪問控制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang+Vue輕松構(gòu)建Web應(yīng)用的方法步驟
本文主要介紹了Golang+Vue輕松構(gòu)建Web應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05