go 解析字符串

场景

有时,我们的程序收到的消息不是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包提供的函数,可以完成基本的字符串解析功能。

常用的解析字符串函数有以下几个:

1.字符串分割函数Split

比如我们例子里那条消息,消息标题和消息内容之间是通过“------\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个子串,第一个子串为消息标题,第二个子串为消息内容,最后一个子串为空字符串 。

2.判断字符串是否包含指定子串Contains

有时候,一个字符串我们并不需要把每个字符都解析出来,我们只需要通过字符串中是否包含特定子串,就可以知道消息中我们需要的信息。

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变量中。

3.查找子串的位置Index

在例子中,如果我们想获取“业务方”信息,首先需要从子串中找到“业务方:”这个子串的位置,然后再获取其后面的内容。

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)
	}
}

注意上面程序中的细节,每个函数执行完,都要考虑到异常情况。程序运行结果如下:

发布了39 篇原创文章 · 获赞 25 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/u013536232/article/details/104196177