Go基于struct?tag實現(xiàn)結(jié)構(gòu)體字段級別的訪問控制
struct tag 是什么?
在Go 中,結(jié)構(gòu)體主要是用于定義復(fù)雜數(shù)據(jù)類型,而 struct tag 則是附加在 struct 字段后的字符串,提供了一種方式來存儲關(guān)于字段的元信息,然后,tag 在程序運行時一般不會直接影響程序邏輯,
如下是一個定義了 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)體字段后,通過反引號 `` 包裹起來的鍵值對形式就可定義它們。
具體有什么用呢?
這個 tag 究竟有什么用呢?為何要定義它們。
單從這個例子中來看,假設(shè)你是在 "encoding/json" 庫中使用 Person 結(jié)構(gòu)體,它是告訴 Go 在處理 JSON 序列化和反序列化時,字段名稱的轉(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。
與其他語言對比的話,雖然 Go 的 struct tag 在某種程度上類似于 Java 的注解或 C# 的屬性,但 Go 的 tag 更加簡潔,并且主要通過反射機制在運行時被訪問。
這種設(shè)計反映了Go語言的哲學(xué):簡單、直接而有效。但確實也是功能有限!
常見使用場景
結(jié)構(gòu)體 tag 在 Go 語言中常見用途,我平時最常見有如下這些。
JSON/XML 序列反序列化
如前面的介紹的案例中,通過 encoding/json 或者其他的庫如 encoding/xml 庫,tag 可以控制如何將結(jié)構(gòu)體字段轉(zhuǎn)換為 JSON 或 XML,或者如何從它們轉(zhuǎn)換回來。
數(shù)據(jù)庫操作
在ORM(對象關(guān)系映射)庫中,tag 可以定義數(shù)據(jù)庫表的列名、類型或其他特性。
如我們在使用 Gorm 時,會看到這樣的定義:
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ù)驗證
在一些庫中,tag 用于驗證數(shù)據(jù),例如,確保一個字段是有效的電子郵件地址。
如下是 govalidator[1] 使用結(jié)構(gòu)體上 tag 實現(xiàn)定義數(shù)據(jù)驗證規(guī)則的一個案例。
type User struct {
Email string `valid:"email"`
Age int `valid:"range(18|99)"`
}在這個例子中,valid tag 定義了字段的驗證規(guī)則,如 email 字段值是否是有效的 email,age 字段是否滿足數(shù)值在 18 到 99 之間等。
我們只要將類型為 User 類型的變量交給 govalidator,它可以根據(jù)這些規(guī)則來驗證數(shù)據(jù),確保數(shù)據(jù)的正確性和有效性。
示例如下:
valid, err := govalidator.ValidateStruct(User{Email: "test@example.com", Age: 20})返回的 valid: 為 true 或 false,如果發(fā)生錯誤,err 提供具體的錯誤原因。
tag 行為自定義
前面展示的都是利用標(biāo)準(zhǔn)庫或三方庫提供的能力,如果想自定義 tag 該如何實現(xiàn)?畢竟有些情況下,如果默認(rèn)提供的 tag 提供的能力不滿足需求,我們還是希望可以自定義 tag 的行為。
這需要了解與理解 Go 的反射機制,它為數(shù)據(jù)處理和元信息管理提供了強大的靈活性。
如下的示例代碼:
type Person struct {
Name string `mytag:"MyName"`
}
t := reflect.TypeOf(Person{})
field, _ := t.FieldByName("Name")
fmt.Println(field.Tag.Get("mytag")) // 輸出: MyName在這個例子中,我們的 Person 的字段 Name 有一個自定義的 tag - mytag,我們直接通過反射就可以訪問它。
這只是簡單的演示如何訪問到 tag。如何使用它呢?
這就要基于實際的場景了,當(dāng)然,這通常也離不開與反射配合。下面我們來通過一個實際的例子介紹。
案例:結(jié)構(gòu)體字段訪問控制
讓我們考慮一個實際的場景:一個結(jié)構(gòu)訪問控制系統(tǒng)。
這個系統(tǒng)中,我們可以根據(jù)用戶的角色(如 admin、user)或者請求的來源(admin、web)控制對結(jié)構(gòu)體字段的訪問。具體而言,假設(shè)我定義了一個包含敏感信息的結(jié)構(gòu)體,我可以使用自定義 tag 來標(biāo)記每個字段的訪問權(quán)限。
是不是想到,這或許可用在 API 接口范圍字段的控制上,防止泄露敏感數(shù)據(jù)給用戶。
接下來,具體看看如何做吧?
定義結(jié)構(gòu)體
我們首先定義一個UserProfile結(jié)構(gòu)體,其中包含用戶的各種信息。每個信息字段都有一個自定義的 access tag,用于標(biāo)識字段訪問權(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 是敏感字段,它只對 admin 角色可見。而 UserName 和 Email 則是所有用戶可見。

到此,結(jié)構(gòu)體 UserProfile 定義完成。
實現(xiàn)權(quán)限控制
接下來就是要實現(xiàn)一個函數(shù),實現(xiàn)根據(jù) UserProfile 定義的 access tag 決定字段內(nèi)容的可見性。
假設(shè)函數(shù)名稱為 FilterFieldsByRole,它接受一個 UserProfile 類型變量和用戶角色,返回內(nèi)容一個過濾后的 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)限控制的重點邏輯部分,就是 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]
這個場景,通過自定義結(jié)構(gòu)體 tag,給予指定角色,很輕松地就實現(xiàn)了一個基于角色的權(quán)限控制。
毫無疑問,這個代碼更加清晰和可維護,而且具有極大靈活性、擴展性。如果想擴展更多角色,也是更加容易。
不過還是要說明下,如果在 API 層面使用這樣的能力,還是要考慮反射可能帶來的性能影響。
總結(jié)
這篇博文介紹了Go語言中結(jié)構(gòu)體 tag 的基礎(chǔ)知識,如是什么,如何使用。另外,還介紹了它們在不同場景下的應(yīng)用。通過簡單的例子和對比,我們看到了 Go 中結(jié)構(gòu)體 tag 的作用。
文章的最后,通過一個實際案例,演示了如何使用 struct tag 使我們代碼更加靈活強大。雖然 struct tag 的使用非常直觀,但正確地利用這些 tag 可以極大提升我們程序的功能和效率
引用鏈接
[1] govalidator: github.com/asaskevich/govalidator
以上就是Go基于struct tag實現(xiàn)結(jié)構(gòu)體字段級別的訪問控制的詳細(xì)內(nèi)容,更多關(guān)于Go struct tag訪問控制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang+Vue輕松構(gòu)建Web應(yīng)用的方法步驟
本文主要介紹了Golang+Vue輕松構(gòu)建Web應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05

