GO语言结构体标签用法

GO语言结构体标签

    • json转换
    • gorm标签指定列名
      • 模型名和表名的映射关系
        • 规则
        • 举例
      • 结构体字段名和列名的对应规则
        • 规则
        • 举例
      • 结构体标签gorm的使用
    • 反射
    • form表单验证
      • 定义结构体
      • 定义接口
      • 验证结构体
      • 调用验证器
      • 具体实现
      • 测试

Go语言结构体标签,就是在结构体中的一段字符串,有点像PHP中的注解,有严格的格式要求,通常用于反射包里的方法来访问它,标签用来声明结构体中字段的属性。

json转换

package main

import (
	"encoding/json"
	"fmt"
)

type User struct {
	Name   string `json:"username"`
	Age    int    `json:"userage"`
}

func main() {
	myself := User{"Skn1fe", 23}
	jsondata, err := json.Marshal(myself)
	if err != nil {
		fmt.Println("格式错误")
	} else {
		fmt.Printf("User结构体转json:%s\n", jsondata)
	}
}

输出结果:

User结构体转json:{"username":"Skn1fe","userage":23}

可以看出"encoding/json"包的json.Marshal()方法作用就是把结构体转换为json,它读取了User结构体里面的标签,json键值对的键为定义的标签名,结构体的名字起了辅助作用,同时定义了字段数据类型。json.Unmarshal()可以把json字符串转换为结构体,在很多第三方包方法都会读取结构体标签。 注意在标签中的外层符号是键盘Tab键上方的键,不是单引号。

gorm标签指定列名

模型名和表名的映射关系

规则
  • 第一个大写字母变为小写;
  • 遇到其他大写字母变为小写并且在前面加下划线;
  • 连着的几个大写字母,只有第一个遵循上面的两条规则,其他的大写字母转为小写,不加下划线,遇到小写,前面的第一个大写字母变小写并加下划线;
  • 复数形式;
举例
  • User => users 首字母小写,复数
  • UserInfo => user_infos
  • DBUserInfo => db_user_infos
  • DBXXXXUserInfo => dbxxxx_user_infos

结构体字段名和列名的对应规则

规则
  • 列名是字段名的蛇形小写
举例
  • Name --> name
  • CreatedTime --> create_time
  • 可以通过gorm标签指定列名,AnimalId int64 gorm:"column:beast_id"

结构体标签gorm的使用

type User struct {
  Name string `gorm:"<-:create"` // 允许读和创建
  Name string `gorm:"<-:update"` // 允许读和更新
  Name string `gorm:"<-"`        // 允许读和写(创建和更新)
  Name string `gorm:"<-:false"`  // 允许读,禁止写
  Name string `gorm:"->"`        // 只读(除非有自定义配置,否则禁止写)
  Name string `gorm:"->;<-:create"` // 允许读和写
  Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
  Name string `gorm:"-"`  // 读写操作均会忽略该字段
}

反射

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Name string `data1:"test1" data2:"test2"`
	Age  string `json:"test"`
}

func main() {
	user := User{"Skn1fe", "23"}
	user_value := reflect.ValueOf(user) //获取变量的值

	user_type := reflect.TypeOf(user) //获取类型
	fmt.Println(user_value, user_type)

	//反射的用法,获取结构体标签的值
	name_data := user_type.Field(0)             //user第一个字段
	name_tag_data := name_data.Tag.Get("data2") //获取结构体第一个字段的Tag(标签)里面键为data2的值
	fmt.Println("name_data:", name_tag_data)
}

输出结果:

{Skn1fe 23} main.User
name_data: test2

form表单验证

  • 自定义结构体标签,使用valid标签作为标志
  • 抽取不同字段的验证方法,例如数字类型字段验证方法,字符类型的验证方法,邮箱类型的验证方法等
  • 动态解析不同的字段调用对应的验证方法,并返回结果

定义结构体

type User struct {
	ID    int    `_`
	Name  string `valid:"string;maxsize(10);minsize(3)"`
	Age   int    `valid:"number;range(0,150)"`
	Email string `valid:"email"`
}

定义接口

// 接口:方法的集合,实现这个接口中的所有方法,就实现了这个接口。
type Validation struct {
	Data   interface{} //接收各种结构体数据,空接口可以接受任何数据类型
	Errors []error
}

验证结构体

// 验证结构体
func (this *Validation) Validate() {
	// todo 编写验证逻辑
	//获取它runtime数据
	rundata := reflect.TypeOf(this.Data)
	//校验数据是否是结构体
	if rundata.Kind() != reflect.Struct {
		panic("数据类型不是结构体")
	}
	//循环遍历结构体字段,获取标签内容,再进行校验
	for i := 0; i < rundata.NumField(); i++ {
		//获取字段的标签
		tag := rundata.Field(i).Tag.Get("valid")
		curValidator := this.getValidator(tag)
		//调用验证器方法,把字段的值传递进去
		//fmt.Println(rundata.Field(i))
		valid, err := curValidator.Validate(reflect.ValueOf(this.Data).Field(i).Interface())
		if !valid && err != nil {
			this.Errors = append(this.Errors, err)
		}
	}
}

调用验证器

var MinRe = regexp.MustCompile(`minsize\((\d+)\)`)
var MaxRe = regexp.MustCompile(`maxsize\((\d+)\)`)

// 通过tag内容返回结构体字段
func (this *Validation) getValidator(tag string) Validator {
	//"string;maxsize(10);minsize(3)"
	//解析tag内容
	tagArgs := strings.Split(tag, ";")
	//根据小标志生成对应验证器
	switch tagArgs[0] {
	case "string":
		strValidator := &StringValidator{}
		for _, str := range tagArgs[1:] {
			switch {
			case MaxRe.MatchString(str):
				fmt.Sscanf(str, "maxsize(%d)", &strValidator.Max)
			case MinRe.MatchString(str):
				fmt.Sscanf(str, "minsize(%d)", &strValidator.Min)
			}
		}
		if strValidator.Min > strValidator.Max {
			panic("最小值不能大于最大值")
		}
		return strValidator
	case "number":
		numberValidator := &NumberValidator{}
		fmt.Sscanf(tagArgs[1], "range(%d,%d)", &numberValidator.Min, &numberValidator.Max)
		if numberValidator.Min > numberValidator.Max {
			panic("最小值不能大于最大值")
		}
		return numberValidator
	case "email":
		return &EmailValidator{}
	default:
		return &DefaultValidator{}
	}
}

具体实现

// 多态通过接口实现
// 定义验证器的接口
type Validator interface {
	//验证器的方法
	Validate(interface{}) (bool, error)
}

// 缺省的验证器类,不验证
type DefaultValidator struct {
}

func (this *DefaultValidator) Validate(data interface{}) (bool, error) {
	return true, nil
}

type StringValidator struct {
	Min int
	Max int
}

func (this *StringValidator) Validate(data interface{}) (bool, error) {
	//把数据从接口类型转化成string类型
	str := data.(string)
	//判断字符串的长度是否在范围内
	length := len(str)
	if length < this.Min || length > this.Max {
		return false, fmt.Errorf("字符串长度不在范围内")
	}
	return true, nil
}

type NumberValidator struct {
	Min int
	Max int
}

func (this *NumberValidator) Validate(data interface{}) (bool, error) {
	number := data.(int)
	if number < this.Min || number > this.Max {
		return false, fmt.Errorf("数字不在范围内")
	}
	return true, nil
}

// 邮箱正则
var emailRegexp = regexp.MustCompile(`^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$`)

// 定义email的验证器
type EmailValidator struct {
}

func (this *EmailValidator) Validate(data interface{}) (bool, error) {
	//把数据从接口类型转化成string类型
	str := data.(string)
	if !emailRegexp.MatchString(str) {
		return false, fmt.Errorf("邮箱格式不正确")
	}
	return true, nil
}

测试

func main() {
	//创建一个用户对象
	user := User{
		ID:    1,
		Name:  "sh",
		Age:   -20,
		Email: "1234567890",
	}
	//创建一个验证器对象
	vd := &Validation{user, nil}
	vd.Validate()
	for _, err := range vd.Errors {
		fmt.Println(err)
	}
}

输出结果:

字符串长度不在范围内
数字不在范围内
邮箱格式不正确

你可能感兴趣的:(golang,开发语言,后端)