Go 中 struct tag 如何用?基于它实现字段级别的访问控制

news2024/11/27 4:18:28

嗨,大家好!本文是系列文章 Go 小技巧第十篇,系列文章查看:Go 语言小技巧。

在 Go 中,结构体主要是用于定义复杂数据类型。而 struct tag 则是附加在 struct 字段后的字符串,提供了一种方式来存储关于字段的元信息。然后,tag 在程序运行时不会直接影响程序逻辑。

本文将会基于这个主题展开,讨论 Go 中的结构体 tag 究竟是什么?我们该如何利用它?另外,文末还提供了一个实际案例,帮助我们进一步提升对 struct tag 的理解。

struct tag 是什么?

如下是一个定义了 tag 的结构体 Person 类型。

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

例子中,json:"name"json:"age" 就是结构体 tag。结构体 tag 的使用非常直观。你只需要在定义结构体字段后,通过反引号 `` 包裹起来的键值对形式就可定义它们。

具体有什么用呢?

这个 tag 究竟有什么用呢?为何要定义它们。

单从这个例子中来看,假设你是在 “encoding/json” 库中使用 Person 结构体,它是告诉 Go 在处理 JSON 序列化和反序列化时,字段名称的转化规则。

让我们通过它在 “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 更加简洁,并且主要通过反射机制在运行时被访问。

这种设计反映了Go语言的哲学:简单、直接而有效。但确实也是功能有限!

在这里插入图片描述

常见使用场景

结构体 tag 在 Go 语言中常见用途,我平时最常见有如下这些。

JSON/XML 序列反序列化

如前面的介绍的案例中,通过 encoding/json 或者其他的库如 encoding/xml 库,tag 可以控制如何将结构体字段转换为 JSON 或 XML,或者如何从它们转换回来。

数据库操作

在ORM(对象关系映射)库中,tag 可以定义数据库表的列名、类型或其他特性。

如我们在使用 Gorm 时,会看到这样的定义:

type User struct {
    gorm.Model
    Name   string `gorm:"type:varchar(100);unique_index"`
    Age    int    `gorm:"index:age"`
    Active bool   `gorm:"default:true"`
}

结构体 tag 可用于定义数据库表的列名、类型或其他特性。

数据验证

在一些库中,tag 用于验证数据,例如,确保一个字段是有效的电子邮件地址。

如下是 govalidator 使用结构体上 tag 实现定义数据验证规则的一个案例。

type User struct {
    Email string `valid:"email"`
    Age   int    `valid:"range(18|99)"`
}

在这个例子中,valid tag 定义了字段的验证规则,如 email 字段值是否是有效的 emailage 字段是否满足数值在 18 到 99 之间等。

我们只要将类型为 User 类型的变量交给 govalidator,它可以根据这些规则来验证数据,确保数据的正确性和有效性。

示例如下:

valid, err := govalidator.ValidateStruct(User{Email: "test@example.com", Age: 20})

返回的 valid:truefalse,如果发生错误,err 提供具体的错误原因。

tag 行为自定义

前面展示的都是利用标准库或三方库提供的能力,如果想自定义 tag 该如何实现?毕竟有些情况下,如果默认提供的 tag 提供的能力不满足需求,我们还是希望可以自定义 tag 的行为。

这需要了解与理解 Go 的反射机制,它为数据处理和元信息管理提供了强大的灵活性。

如下的示例代码:

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。如何使用它呢?

这就要基于实际的场景了,当然,这通常也离不开与反射配合。下面我们来通过一个实际的例子介绍。

案例:结构体字段访问控制

让我们考虑一个实际的场景:一个结构访问控制系统。

这个系统中,我们可以根据用户的角色(如 admin、user)或者请求的来源(admin、web)控制对结构体字段的访问。具体而言,假设我定义了一个包含敏感信息的结构体,我可以使用自定义 tag 来标记每个字段的访问权限。

是不是想到,这或许可用在 API 接口范围字段的控制上,防止泄露敏感数据给用户。

接下来,具体看看如何做吧?

定义结构体

我们首先定义一个UserProfile结构体,其中包含用户的各种信息。每个信息字段都有一个自定义的 access tag,用于标识字段访问权限(adminuser)。

type UserProfile struct {
    Username    string `access:"user"`  // 所有用户可见
    Email       string `access:"user"`  // 所有用户可见
    PhoneNumber string `access:"admin"` // 仅管理员可见
    Address     string `access:"admin"` // 仅管理员可见
}

其中,PhoneNumberAddress 是敏感字段,它只对 admin 角色可见。而 UserNameEmail 则是所有用户可见。

到此,结构体 UserProfile 定义完成。

实现权限控制

接下来就是要实现一个函数,实现根据 UserProfile 定义的 access tag 决定字段内容的可见性。

假设函数名称为 FilterFieldsByRole,它接受一个 UserProfile 类型变量和用户角色,返回内容一个过滤后的 map(由 fieldname 到 fieldvalue 组成的映射),其中只包含角色有权访问的字段。

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() 
            // 组织返回结果 result
            result[fieldName] = fieldValue
        }
    }
    return result
}

权限控制的重点逻辑部分,就是 if accessTag == "user" || accessTag == role 这段判断条件。当满足条件之后,接下来要做的就是通过反射获取字段名称和值,并组织目标的 Map 类变量 result了。

使用演示

让我们来使用下 FilterFieldsByRole 函数,检查下是否满足按角色访问特定的用户信息的功能。

示例代码如下:

func main() {
    profile := UserProfile{
        Username:    "johndoe",
        Email:       "johndoe@example.com",
        PhoneNumber: "123-456-7890",
        Address:     "123 Elm St",
    }

    // 假设当前用户是普通用户
    userInfo := FilterFieldsByRole(profile, "user")
    fmt.Println(userInfo)

    // 假设当前用户是管理员
    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]

这个场景,通过自定义结构体 tag,给予指定角色,很轻松地就实现了一个基于角色的权限控制。

毫无疑问,这个代码更加清晰和可维护,而且具有极大灵活性、扩展性。如果想扩展更多角色,也是更加容易。

不过还是要说明下,如果在 API 层面使用这样的能力,还是要考虑反射可能带来的性能影响。

总结

这篇博文介绍了Go语言中结构体 tag 的基础知识,如是什么,如何使用。另外,还介绍了它们在不同场景下的应用。通过简单的例子和对比,我们看到了 Go 中结构体 tag 的作用。

文章的最后,通过一个实际案例,演示了如何使用 struct tag 使我们代码更加灵活强大。虽然 struct tag 的使用非常直观,但正确地利用这些 tag 可以极大提升我们程序的功能和效率。

博文地址:Go 中 struct tag 如何用?基于它实现字段级别的访问控制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1425951.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux服务器上安装mysql

今天在华为云上安装mysql&#xff0c;安装命令很简单&#xff0c;就一行命令&#xff1a;sudo apt-get update && sudo apt-get install mysql-server 我安装的是mysql8.0版本&#xff0c;这里主要说一下安装mysql后怎么在外网连接&#xff1a; 1、注释掉 bind-add…

CoroNa Green acetoxymethyl (AM) ester,具有良好的细胞膜穿透能力

CoroNa Green, AM, Cell Permeant&#xff0c;CoroNa Green acetoxymethyl (AM) ester&#xff0c;CoroNa Green, AM&#xff0c;Sodium indicator 钠离子指示剂&#xff08;荧光探针&#xff09;&#xff0c;具有良好的细胞膜穿透能力&#xff0c;能够检测到细胞内钠离子的微小…

Redis(二)(实战篇)

查漏补缺 1.线程池 概述 : 提到池&#xff0c;大家应该能想到的就是水池。水池就是一个容器&#xff0c;在该容器中存储了很多的水。那么什么是线程池呢&#xff1f;线程池也是可以看做成一个池子&#xff0c;在该池子中存储很多个线程。 线程池存在的意义&#xff1a; 系…

go并发编程-介绍与Goroutine使用

1. 并发介绍 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中的多个…

排序之计数排序

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

Java基础 集合(一)Collection与Iterator Iterable迭代器详解以及集合在for循环下如何删除

目录 简介 Iterator和Iterable for循环删除问题 for-each下删除数据 解决方法 for下删除数据 解决方法 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们往往容易陷入工作的…

非鸿蒙官方低代码源码生成器

介绍 鸿蒙低代码可视化开发神器快速对鸿蒙ArkUI生成源码&#xff0c;结合类似小程序类似设计&#xff0c;页面设计底部菜单&#xff0c;支持宫格组件、轮播图、图文列表、图片组件、文本内容组件&#xff0c;快速对接第三方HttpApi。通过鸿蒙扩展axios扩展库加载数据源&#x…

众星素颜出镜影协大会,真实状态引发热议,陈道明备受赞誉。

♥ 为方便您进行讨论和分享&#xff0c;同时也为能带给您不一样的参与感。请您在阅读本文之前&#xff0c;点击一下“关注”&#xff0c;非常感谢您的支持&#xff01; 文 |猴哥聊娱乐 编 辑|徐 婷 校 对|侯欢庭 岁末之际&#xff0c;电影家协会盛大集结&#xff0c;众星璀璨…

[JAVA数据结构] 二叉树 - 详解

目录 (一) 树形结构 1. 树的概念与结构 2. 树的表示形式 (二) 二叉树 1. 二叉树的概念 2. 两种特殊的二叉树 3. 二叉树的性质 4. 二叉树的存储 5. 二叉树的遍历 (1) 前序遍历 (2)中序遍历 (3)后序遍历 6. 二叉树的基本操作 - 模拟实现 (一) 树形结构 1. 树的概念与…

机器学习算法决策树

决策树的介绍 决策树是一种常见的分类模型&#xff0c;在金融风控、医疗辅助诊断等诸多行业具有较为广泛的应用。决策树的核心思想是基于树结构对数据进行划分&#xff0c;这种思想是人类处理问题时的本能方法。例如在婚恋市场中&#xff0c;女方通常会先询问男方是否有房产&a…

c/c++静态字符串的基本操作

文章目录 1.串的定义2. 赋值操作3.字符串复制操作4.判断空5.比较操作6.求串的长度7.打印全部元素8.求字串9.串拼接10.清空操作11.源代码 在本篇博客中&#xff0c;定义的字符串为索引1为起始下标。 1.串的定义 #define MAXLEN 255 //预定义最大串长255 typedef struct {char …

从[redis:LinkedList]中学习链表

文章目录 adlistlistNodelistmacros[宏定义]listCreatelistInitNodelistEmptylistReleaselistAddNodeHeadlistLinkNodeHeadlistAddNodeTaillistLinkNodeTaillistInsertNodelistDelNodelistUlinkNodelistIndexredis3.2.100quicklistredis7.2.2quicklist redis的基本数据类型之一…

【代码随想录】LC 77. 组合

文章目录 前言一、题目1、原题链接2、题目描述 二、解题报告1、思路分析2、代码详解 前言 本专栏文章为《代码随想录》书籍的刷题题解以及读书笔记&#xff0c;如有侵权&#xff0c;立即删除。 一、题目 1、原题链接 77. 组合 2、题目描述 二、解题报告 1、思路分析 &#x…

C++类和对象(中)六个默认成员函数

&#x1f308;类的六个默认成员函数 任何一个类&#xff0c;不管是否为空&#xff0c;都会在生成的时候默认调用六个成员函数&#xff0c;这些成员函数可以自动生成&#xff0c;也可以由程序员写出。这六个默认成员函数分别是&#xff1a; 最主要的是前四个&#xff1a; 初始…

2-树-恢复二叉搜索树

这是树的第二篇算法&#xff0c;力扣链接。 给你二叉搜索树的根节点 root &#xff0c;该树中的 恰好 两个节点的值被错误地交换。请在不改变其结构的情况下&#xff0c;恢复这棵树 。 示例 1&#xff1a; 输入&#xff1a;root [1,3,null,null,2] 输出&#xff1a;[3,1,null,…

Google Gemini Pro 国内版

Google Gemini Pro 国内版&#xff1a;【直达链接】 Google Gemini Pro 国内版 能力分类基准测试描述更高分数更好Gemini UltraGPT-4通用MMLU57个主题&#xff08;包括STEM、人文等&#xff09;的问题表示是90.0%86.4%&#xff08;5-shot, 报告&#xff09;推理Big-Bench Hard…

linux系统上C程序的编译、运行及调试-gcc

gcc -o timer timer.c &#xff1a;生成可执行文件main&#xff0c;依托main.c,也可依托多个文件./timer :运行代码

C# 根据USB设备VID和PID 获取设备总线已报告设备描述

总线已报告设备描述 DEVPKEY_Device_BusReportedDeviceDesc 模式 winform 语言 c# using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Window…

【软件设计师笔记】计算机系统基础知识考点

&#x1f413; 计算机系统组成 计算机系统是由硬件和软件组成的&#xff0c;它们协同工作来运行程序。计算机的基本硬件系统由 运算器、控制器、存储器、输入设备和输出设备5大部件组成。运算器、控制器等部件被集成 在一起统称为中央处理单元&#xff08;Central Processing …

幻兽帕鲁服务器怎么更新?教你一行命令搞定!

今天发现幻兽帕鲁又进行了一次更新&#xff0c;所以我们的服务器也是需要同步更新的 那么&#xff0c;我们通过阿里云或腾讯云一键部署的幻兽帕鲁服务器怎么更新呢&#xff1f; 腾讯云轻量云一键部署幻兽帕鲁服务器教程&#xff1a;https://curl.qcloud.com/pzBO9wN7 先来讲…