Featured image of post 在grom中使用反射实现动态建表

在grom中使用反射实现动态建表

在gorm中使用反射机制实现依据运行时获得的数据来创建数据表。

在我的毕设 “网络安全日志采集储存系统” 中,我遇到了一个需求——从外部文件中加载模板,并以此对从多台服务器上采集到的nginx访问日志(access log)进行正则提取,获得需要的字段并最终储存到 mysql 中。这里存在着一个问题,由于需要提取的字段以及字段的命名都来源于外部文件,在程序编译时是没办法知道的,所以我们需要一个机制来实现动态创建 mysql 的表结构以及后续的插入与查询操作。

其实这个需求大可以分析模板后生成 mysql 语句来执行建表,但是由于我比较懒…并且这个项目中的其他部分也用到了 gorm 这个orm框架,所以我还是打算通过gorm来实现自动建表。

使用反射动态创建用于gorm的结构体

说到要在 运行时 创建出结构体,自然会想到反射,这里也是我第一次在实际中使用反射,代码如下:

// 模板中对字段的定义
type FieldContainer struct {
	Index     int    `json:"index"`
	Type      string `json:"type"`
	FieldName string `json:"field"`
	Tags      string `json:"tags"`
}

// 用来储存模板的结构体
type Template struct {
	TemplateName string           `json:"templateName"`
	Example      string           `json:"example"`
	Grouper      string           `json:"grouper"`
	TimeLayout   string           `json:"timeLayout"`
	GroupRegexp  *regexp.Regexp   `json:"-"`
	Fields       []FieldContainer `json:"fields"`
}

// 使用反射构造一个结构体,目前仅用于自动建表
func (t *Template) LogStruct() interface{} {
	var fields []reflect.StructField
	fields = append(fields, reflect.StructField{
		Name: "ID",
		Type: reflect.TypeOf(uint(1)),
		Tag:  `gorm:"primarykey"`,
	})
	fields = append(fields, reflect.StructField{
		Name: "Node",
		Type: reflect.TypeOf(""),
		Tag:  `json:"node"`,
	})

	for _, f := range t.Fields {
		var fi = reflect.StructField{
			Name: f.FieldName,
		}
		switch f.Type {
		case "string":
			fi.Type = reflect.TypeOf("")
		case "int":
			fi.Type = reflect.TypeOf(0)
		case "float":
			fi.Type = reflect.TypeOf(0.0)
		case "time":
			fi.Type = reflect.TypeOf(time.Time{})
		}
		fields = append(fields, fi)
	}

	nt := reflect.StructOf(fields)
	return reflect.New(nt).Interface()
}

在上面的代码中,我根据模板中对于字段的标识设置了字段的名字和类型信息,并返回了一个 interface{}变量。

在gorm中使用结构体建表

在得到了一个由运行时获取的信息创建的结构体后,我们可以将其用来创建我们的数据表,使用Table方法指定表名,并通过AutoMigrate方法来实现表的建立即可。

// 当我们首次将 日志主题+日志模板标识的日志卷注册到系统中时,需要为其建一张表
func CreateTableByThemeAndTemplate(theme string, t *template.Template) error {
	err := db.Table(theme + "_" + t.TemplateName).AutoMigrate(t.LogStruct())
	return err
}

后续的插入与查询

尽管在自动建表时用了反射+orm,但是在插入数据的时候我直接使用了 map[string]interface{} 的形式来向对应的表插入数据,因为按照正则与模板对原始日志进行提取后,正好可以得到一张map,并且gorm对使用map进行插入数据也有很好的支持,所以直接使用 map 进行数据的插入即可。 后续的查询等操作也可以通过动态构建 map 来实现。

comments powered by Disqus
本站访客数:
Built with Hugo
Theme Stack designed by Jimmy