有时,我们的程序收到的消息不是json或者xml这样的常用消息体格式,就是一个普通的string,比如下面这条消息:
A调用失败率过高
------
业务方:X项目
时间点:2020-01-10 16:25:00.360047+08:00
------
对程序而言,就是这样一个字符串:
"A调用失败率过高\n------\n业务方:X项目\n时间点:2020-01-10 16:25:00.360047+08:00\n------\n"
这样的字符串无法使用json或者xml包提供的函数去解析,只能我们手写解析代码。
我们的需求是,解析出报警类型,业务方,以及时间点,存到一个map里,这样后续的处理就会更加方便。
使用strings包提供的函数,可以完成基本的字符串解析功能。
常用的解析字符串函数有以下几个:
比如我们例子里那条消息,消息标题和消息内容之间是通过“------\n”这个子串来隔开的,那么当我们解析这条消息的时候,第一步应该是把标题和消息内容分别解析出来。
strings包里用于分割字符串的函数是Split,用法如下:
message := strings.Split(src, sep)
//src:需要被分割的字符串
//sep:用于分割字符串的子串
//message:返回的是一个string类型的数组,存储分割之后的多个子串
用例子里的消息做个测试:
message := strings.Split(msg, "\n------\n")
fmt.Println(len(message))
for msgId, subMsg := range message {
fmt.Println(msgId, subMsg)
}
结果如下:
可以看到,字符串中有两个分割子串“\n------\n”,所以被分割为了3个子串,第一个子串为消息标题,第二个子串为消息内容,最后一个子串为空字符串 。
有时候,一个字符串我们并不需要把每个字符都解析出来,我们只需要通过字符串中是否包含特定子串,就可以知道消息中我们需要的信息。
strings包中,判断子串是否存在的函数是Contains,用法如下:
res := strings.Contains(src, substr)
//src:源字符串
//substr:想判断是否存在的子串
//res:判断结果,bool类型,存在为true,不存在为false
还是用例子里的消息测试,比如我们想从消息标题(上一步已经通过split获取)中获取调用类型,可以这样写:
if strings.Contains(message[0], "A调用") {
callType = "callA"
} else if strings.Contains(message[0], "B调用") {
callType = "callB"
} else if strings.Contains(message[0], "C调用") {
callType = "callC"
} else {
return "", nil, errors.New("Unknown call.")
}
这样我们就获取到了调用类型,并将其存入了callType变量中。
在例子中,如果我们想获取“业务方”信息,首先需要从子串中找到“业务方:”这个子串的位置,然后再获取其后面的内容。
strings包中用于查找子串位置的函数是Index,用法如下:
loc := strings.Index(src, substr)
//src:源字符串
//substr:需要查找的字符串
//loc:被查找字符串第一次出现的位置,如果未找到,返回-1
从示例字符串获取“业务方”的测试程序如下:
messageLen := len(message)
if loc := strings.Index(message, "业务方"); loc < messageLen {
if endLoc := strings.Index(message[loc:], "\n"); endLoc > 10 {
value := message[loc+10 : loc+endLoc]
return value
}
return ""
}
程序中,在找到“业务方”出现的位置之后,需要从这个位置开始查找第一个“\n”,因为在这个“\n”前面的是业务方信息,这里需要提醒的是,strings.Index(message[loc:], "\n")这一句的结果,是相对于loc开始的子串的位置,而不是在整个message的位置,这一点初次写很容易遗忘,因此要注意。
最后给出解析例子中字符串的完整程序:
package main
import (
"errors"
"fmt"
"strings"
)
const alertMsg = "A调用失败率过高\n------\n业务方:X项目\n时间点:2020-01-10 16:25:00.360047+08:00\n------\n"
func getValueByKey(message, key string) string {
messageLen := len(message)
if loc := strings.Index(message, key); loc < messageLen {
if endLoc := strings.Index(message[loc:], "\n"); endLoc > 10 {
value := message[loc+10 : loc+endLoc]
return value
}
return ""
}
return ""
}
func parseMsg(msg string) (string, []map[string]string, error) {
//根据分割线分割字符串,第一个子串是标题,后面的子串为报警消息内容
message := strings.Split(msg, "------\n")
if len(message) < 3 {
return "", nil, errors.New("Parse alert message failed.")
}
//根据报警标题确定调用类型
var callType string
if strings.Contains(message[0], "A调用") {
callType = "callA"
} else if strings.Contains(message[0], "B调用") {
callType = "callB"
} else if strings.Contains(message[0], "C调用") {
callType = "callC"
} else {
return "", nil, errors.New("Unknown call.")
}
//根据报警消息内容解析业务方,时间
var alertInfo []map[string]string
for i := 1; i < len(message) - 1; i++ {
tempInfo := make(map[string]string)
tempInfo["project"] = getValueByKey(message[i], "业务方")
tempInfo["time"] = getValueByKey(message[i], "时间点")
if tempInfo["project"] == "" || tempInfo["time"] == "" {
return "", nil, errors.New("Parse alert info failed.")
}
alertInfo = append(alertInfo, tempInfo)
}
return callType, alertInfo, nil
}
func main() {
//fmt.Println(alertMsg)
callType, alertInfo, err := parseMsg(alertMsg)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(callType, alertInfo)
}
}
注意上面程序中的细节,每个函数执行完,都要考虑到异常情况。程序运行结果如下: