From 9a0e3289ba5b9155a478e6ff2f898c6710eb95a4 Mon Sep 17 00:00:00 2001 From: DesistDaydream <39847025+DesistDaydream@users.noreply.github.com> Date: Fri, 8 Nov 2024 21:04:49 +0800 Subject: [PATCH] =?UTF-8?q?feature:=20=E6=B7=BB=E5=8A=A0=E5=A4=9A=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E8=83=BD=E5=8A=9B=EF=BC=8C=E4=BB=A5=E9=80=82=E5=BA=94?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E5=91=8A=E8=AD=A6=E4=BD=BF=E7=94=A8=E4=B8=8D?= =?UTF-8?q?=E5=90=8C=E6=A8=A1=E6=9D=BF=E5=9C=BA=E6=99=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 ++++++++++++++- VERSION | 2 +- a2w.go | 38 +++++++++++++++++++++++++++----------- templates/base_two.tmpl | 24 ++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 templates/base_two.tmpl diff --git a/README.md b/README.md index 5c63f2f..994ae5a 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,25 @@ docker run --name a2w -d -p 5001:5001 -e TZ=Asia/Tokyo rea1shane/a2w ### 消息模板 -消息模板决定了企业微信机器人发出的消息格式,在启动 A2W 时通过 `--template` 指定模板。默认模板的使用说明见 [文档](https://github.com/rea1shane/a2w/blob/main/templates/base.md)。 +消息模板决定了企业微信机器人发出的消息格式,在启动 A2W 时通过 `--template` 指定模板文件所在目录。默认模板的使用说明见 [文档](https://github.com/rea1shane/a2w/blob/main/templates/base.md)。 > [!NOTE] > > 因为企业微信机器人接口限制单条消息的最大长度为 4096,所以本软件会对大于此限制的长消息进行分段。如果你使用自定义模板,请在想要分段的地方留一个空行(在企业微信中,至少三个连续的 `\n` 才被认为是一个空行),以便本软件对消息进行正确的分段。 +使用 tmpl URL Query 可以指定要使用的其他模板(以便适应想要让不同的告警使用不同的模板场景): + +```yaml +receivers: + - name: "a2w" + webhook_configs: + - url: "http://{a2w_address}/send?tmpl=base_two&key={key}" +``` + +> [!Attention] +> +> 模板文件必须以 .tmpl 作为后缀,前缀要符合 URL 字符规范 [RFC 3986, Uniform Resource Identifier(URI): Generic Syntax](https://www.rfc-editor.org/rfc/rfc3986.html)。不要使用中文作为文件名。 + ### 用户提醒 A2W 支持用户提醒功能,修改 Alertmanager 中的配置如下: diff --git a/VERSION b/VERSION index 341cf11..7dff5b8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.0 \ No newline at end of file +0.2.1 \ No newline at end of file diff --git a/a2w.go b/a2w.go index 787a7a5..8715e5c 100644 --- a/a2w.go +++ b/a2w.go @@ -3,11 +3,11 @@ package main import ( "bytes" "encoding/json" - "errors" "flag" "fmt" "io" "net/http" + "path/filepath" "strconv" "strings" "text/template" @@ -53,15 +53,17 @@ const ( ) var ( - tmplPath, tmplName string - logger *logrus.Logger + tmplDir, tmplName string + // key: 模板文件名称; value: 模板文件路径 + tmplFiles map[string]string = make(map[string]string) + logger *logrus.Logger ) func main() { // 解析命令行参数 logLevel := flag.String("log-level", "info", "日志级别。可选值:debug, info, warn, error") addr := flag.String("addr", ":5001", "监听地址。格式: [host]:port") - flag.StringVar(&tmplPath, "template", "./templates/base.tmpl", "模板文件路径。") + flag.StringVar(&tmplDir, "template", "./templates", "模板文件所在目录。") flag.Parse() // 解析日志级别 @@ -70,9 +72,16 @@ func main() { logrus.Panicf("日志级别解析失败: %s", *logLevel) } - // 解析模板文件名称 - split := strings.Split(tmplPath, "/") - tmplName = split[len(split)-1] + // 解析模板文件名称,获取所有后缀为 .tmpl 的文件 + files, err := filepath.Glob(filepath.Join(tmplDir, "*.tmpl")) + if err != nil || len(files) == 0 { + logrus.Fatalf("无法从 %s 目录获取到模板文件: %v", tmplDir, err) + } + for _, file := range files { + split := strings.Split(file, "/") + tmplName := split[len(split)-1] + tmplFiles[tmplName] = file + } // 创建 logger logger = logrus.New() @@ -100,7 +109,14 @@ func health(c *gin.Context) { func send(c *gin.Context) { // 获取 bot key key := c.Query("key") - + // 获取模板名称 + tmplNamePrefix := c.Query("tmpl") + if tmplNamePrefix == "" { + tmplName = "base.tmpl" + } else { + tmplName = fmt.Sprintf("%v.tmpl", tmplNamePrefix) + } + logrus.Debugf("将要使用的模板: %v", tmplFiles[tmplName]) // 获取提醒列表 mentions, exist := c.GetQueryArray("mention") var mentionsBuilder strings.Builder @@ -138,7 +154,7 @@ func send(c *gin.Context) { tfm["timeFormat"] = timeFormat tfm["timeDuration"] = timeDuration tfm["timeFromNow"] = timeFromNow - tmpl := template.Must(template.New(tmplName).Funcs(tfm).ParseFiles(tmplPath)) + tmpl := template.Must(template.New(tmplName).Funcs(tfm).ParseFiles(tmplFiles[tmplName])) var content bytes.Buffer if err := tmpl.Execute(&content, notification); err != nil { e := c.Error(err) @@ -169,7 +185,7 @@ func send(c *gin.Context) { for _, fragment := range fragments { // 切割后的单条消息都过长 if len(fragment)+len(emptyLine) > snippetMaxLen { - e := c.Error(errors.New(fmt.Sprintf("切割后的消息长度 %d 仍超出片段长度限制 %d", len(fragment), snippetMaxLen-len(emptyLine)))) + e := c.Error(fmt.Errorf("切割后的消息长度 %d 仍超出片段长度限制 %d", len(fragment), snippetMaxLen-len(emptyLine))) e.Meta = "分段消息失败" c.Writer.WriteHeader(http.StatusBadRequest) return @@ -223,7 +239,7 @@ func send(c *gin.Context) { wecomRespBody, _ := io.ReadAll(wecomResp.Body) wecomResp.Body.Close() if wecomResp.StatusCode != http.StatusOK || string(wecomRespBody) != okMsg { - e := c.Error(errors.New(string(wecomRespBody))) + e := c.Error(fmt.Errorf(string(wecomRespBody))) e.Meta = "请求企业微信失败,HTTP Code: " + strconv.Itoa(wecomResp.StatusCode) c.Writer.WriteHeader(http.StatusInternalServerError) return diff --git a/templates/base_two.tmpl b/templates/base_two.tmpl new file mode 100644 index 0000000..84076a9 --- /dev/null +++ b/templates/base_two.tmpl @@ -0,0 +1,24 @@ +二号模板 +{{ range $i, $alert := .Alerts }} + + {{- if eq $alert.Status "firing" }} +**[firing] {{ $alert.Labels.alertname }}** + {{- if $alert.Labels.level }} +**警报等级**: {{ $alert.Labels.level }} + {{- end }} +**触发时间**: {{ timeFormat ($alert.StartsAt) }} +**持续时长**: {{ timeFromNow ($alert.StartsAt) }} + {{- if $alert.Annotations.current }} +**当前状态**: {{ $alert.Annotations.current }} + {{- end }} + {{- else if eq $alert.Status "resolved"}} +**[resolved] {{ $alert.Labels.alertname }}** +**触发时间**: {{ timeFormat ($alert.StartsAt) }} +**恢复时间**: {{ timeFormat ($alert.EndsAt) }} +**持续时长**: {{ timeDuration ($alert.StartsAt) ($alert.EndsAt) }} + {{- end }} + {{- if $alert.Annotations.labels }} +**标签列表**: {{ $alert.Annotations.labels }} + {{- end }} + +{{ end }}