在我的毕设 “网络安全日志采集储存系统” 中,我遇到了一个需求——从外部文件中加载模板,并以此对从多台服务器上采集到的nginx访问日志(access log)进行正则提取,获得需要的字段并最终储存到 mysql 中。这里存在着一个问题,由于需要提取的字段以及字段的命名都来源于外部文件,在程序编译时是没办法知道的,所以我们需要一个机制来实现动态创建 mysql 的表结构以及后续的插入与查询操作。
其实这个需求大可以分析模板后生成 mysql 语句来执行建表,但是由于我比较懒…并且这个项目中的其他部分也用到了 gorm 这个orm框架,所以我还是打算通过gorm来实现自动建表。
使用反射动态创建用于gorm的结构体
说到要在 运行时 创建出结构体,自然会想到反射,这里也是我第一次在实际中使用反射,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| // 模板中对字段的定义
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方法来实现表的建立即可。
1
2
3
4
5
| // 当我们首次将 日志主题+日志模板标识的日志卷注册到系统中时,需要为其建一张表
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 来实现。