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