最近在开发一个通用消息通知工具,可以从 API 、数据库等数据源获取数据,然后根据数据渲染消息模板,然后通过企微、邮件、短信、电话语音等渠道推送消息。整个工作的设计思想,就是想提供一种低代码、配置化的消息通知解决方案。
简言之,工具对日常消息通知开发场景进行抽象建模,让所有要素都支持配置:
- 数据源
- 消息模板
- 通知渠道
- 通知对象
- 发送策略(手动触发或定时任务)
那在开发的过程中,就不可避免要面对数据提取问题。比如,有个系统提需求说他们的变更单需要每天催一次,列表通过 API 给我,结构如下:
1
2
3
4
5
6
7
|
{
"success": true,
"data": [
{"name": "变更①"},
{"name": "变更②"}
]
}
|
消息通知工具需要支持灵活的数据提取,将待催办变更列表从 API 返回数据中提取出来。因为我肯定无法为每个对接接口的数据格式都对接一遍。如果另一个系统的字段名是不一样的呢?
1
2
3
4
5
6
7
|
{
"Success": true,
"Data": [
{"name": "数据维护①"},
{"name": "数据维护②"}
]
}
|
如果数据源存在很深的嵌套呢?
1
2
3
4
5
6
7
8
9
10
|
{
"Success": true,
"Result": {
"Count": 10,
"Data": [
{"name": "数据维护①"},
{"name": "数据维护②"}
]
}
}
|
想要提取数据,最好有一套比较灵活的数据访问语法,类似 Go 标准库中的 template 模板那样。考虑到我们没有精力为此开发一套提取引擎,我们决定借助 template 标准库来干这个事。
工具我只负责设计,代码实施由一个徒弟负责,他先想到 JSON 序列:
1
|
{{ jsonify .Result.Data }}
|
- 渲染模板时提供工具函数 jsonify ,将数据转化成 JSON 格式;
- 对渲染结果做 JSON 反序列化,还原成原来的数据;
这个方案比较绕,但开会时我一时半会也没想到更好的方式,就先这样了。虽然这阵子还有很多其他事情在忙,但这个事一直在我心头,因为它有两个重大缺陷:
- JSON 序列化和反序列化肯定有不少性能开销,虽然目前基本没什么影响;
- JSON 序列化可能让数据丧失原有类型,退化成基本类型;
昨晚睡觉前迷迷糊糊,突然灵机一动!——我可以提供一个工具函数来接数据!于是早上起来写下了这段代码:
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
53
54
|
package main
import (
"fmt"
"io/ioutil"
"text/template"
)
type DataContainer struct {
d interface{}
}
func NewDataContainer() *DataContainer {
return &DataContainer{}
}
func (c *DataContainer) Get() interface{} {
return c.d
}
func (c *DataContainer) Set(d interface{}) *DataContainer {
c.d = d
return c
}
type JsonMap map[string]interface{}
func main() {
data := JsonMap{
"Success": true,
"Data": JsonMap{
"User": JsonMap{
"Name": "fasionchan",
"Language": "Go",
},
},
}
container := NewDataContainer()
funcs := template.FuncMap{
"setData": container.Set,
}
t, err := template.New("demo").Funcs(funcs).Parse("{{ setData .Data.User }}")
if err != nil {
panic(err)
}
if err := t.Execute(ioutil.Discard, data); err != nil {
panic(err)
}
fmt.Println(container.Get())
}
|
这个程序设计了一个数据容器 DataContainer ,用来存放提取的数据,它只提供了 Get 和 Set 方法;将 Set 方法传给模板引擎,通过标准模板语法将要提取出来的数据 Set 到数据容器;最后调用 Get 方法就可以拿到提取出来的数据!
我们的目的是提取数据,模板引擎的渲染结果是无关紧要的。因此这个程序直接用 ioutil.Discard ,忽略渲染结果。
借助模板引擎来提取数据,这个思路是不是有种剑走偏锋的意思?哈哈!
虽然目的达到了,但用起来还是比较繁琐。是不是有更完美的方案呢?我没搜到,就先这样罢。
订阅更新,获取更多学习资料,请关注我们的公众号: