Skip to content

Commit

Permalink
修复发送给企业微信的消息内容过长导致请求失败
Browse files Browse the repository at this point in the history
Merge pull request #2 from rea1shane/fix-wecom-content-too-long
  • Loading branch information
rea1shane committed Jul 5, 2023
2 parents 0ff41bd + 989a954 commit b8e11a9
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 27 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 变更日志

本项目所有值得注意的变化都将记录在这个文件中。

本文件格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),并且本项目遵守 [Semantic Versioning](https://semver.org/lang/zh-CN/) 版本更新规则。

## [0.1.4](https://github.com/rea1shane/a2w/compare/0.1.3...0.1.4) - 2023-07-05

### 已修复

- 修复发送给企业微信的消息内容过长导致请求失败 ([#1](https://github.com/rea1shane/a2w/issues/1))
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@

消息模板决定了企业微信机器人发出的消息格式,修改 `Makefile` 中的 `TEMPLATE` 变量的值来选择模板。

因为企业微信机器人接口限制单条消息的最大长度为 4096,所以本软件会对大于此限制的长消息进行分段,如果你使用自定义模板,请让每个告警实例之间留有一行空行(表现为 tmpl 文件中 `range` 语句的 `{{ end }}` 为单独的一行,参考 [`base.tmpl`](https://github.com/rea1shane/a2w/blob/main/templates/base.tmpl#L14)),以便本软件对消息进行正确的分段。

### `base.tmpl`

若要使用该消息模板,在告警规则定义中必须包含:
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.3
0.1.4
101 changes: 75 additions & 26 deletions a2w.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ const (
)

var (
tmpl, tmplName string
tmplPath, tmplName string
)

func main() {
port := flag.Int("port", 5001, "监听端口")
flag.StringVar(&tmpl, "template", "./templates/base.tmpl", "模板文件")
flag.StringVar(&tmplPath, "template", "./templates/base.tmpl", "模板文件")
flag.Parse()

split := strings.Split(tmpl, "/")
split := strings.Split(tmplPath, "/")
tmplName = split[len(split)-1]

logger := logrus.New()
Expand Down Expand Up @@ -98,7 +98,7 @@ func send(c *gin.Context) {
tfm["timeFormat"] = timeFormat
tfm["timeDuration"] = timeDuration
tfm["timeFromNow"] = timeFromNow
tmpl := template.Must(template.New(tmplName).Funcs(tfm).ParseFiles(tmpl))
tmpl := template.Must(template.New(tmplName).Funcs(tfm).ParseFiles(tmplPath))
var content bytes.Buffer
if err := tmpl.Execute(&content, notification); err != nil {
e := c.Error(err)
Expand All @@ -107,30 +107,79 @@ func send(c *gin.Context) {
return
}

// 请求企业微信
postBody, _ := json.Marshal(map[string]interface{}{
"msgtype": "markdown",
"markdown": map[string]interface{}{
"content": content.String(),
},
})
postBodyBuffer := bytes.NewBuffer(postBody)
wecomResp, err := http.Post(webhookUrl+key, "application/json", postBodyBuffer)
if err != nil {
e := c.Error(err)
e.Meta = "发起企业微信请求错误"
c.Writer.WriteHeader(http.StatusInternalServerError)
return
// 消息分段
// 为了解决企业微信 Markdown 消息体 4096 长度限制问题
var msgs []string
if content.Len() <= 4096 {
msgs = append(msgs, content.String())
} else {
// 分段消息标识头
msgHeader := `<font color="comment">**(%d/%d)**</font>`
// 单条分段最大长度,预留一些空间用于添加分段头和容错
msgMaxLen := 4096 - 128
// 分段条数
// 因为企业微信机器人接口每分钟频率是 20 条,当消息分段超过 20 条时可能会有部分消息发送失败
msgsLen := content.Len()/msgMaxLen + 1

// 消息切割
contentSnippets := bytes.Split(content.Bytes(), []byte("\n\n"))

// 消息构造器
var msgBuffer bytes.Buffer
msgIndex := 1
msgBuffer.Write([]byte(fmt.Sprintf(msgHeader, msgIndex, msgsLen)))

// 拼接消息
for _, contentSnippet := range contentSnippets {
// 切割后的单条消息都过长
if len(contentSnippet) > msgMaxLen {
e := c.Error(errors.New(fmt.Sprintf("单条告警消息长度 %d 仍超出片段长度限制 %d", len(contentSnippet), msgMaxLen)))
e.Meta = "消息分段失败"
c.Writer.WriteHeader(http.StatusBadRequest)
return
}

// 拼接消息后超出限制长度
if msgBuffer.Len()+len(contentSnippet) > msgMaxLen {
msgs = append(msgs, msgBuffer.String())
msgBuffer.Reset()
msgIndex++
msgBuffer.Write([]byte(fmt.Sprintf(msgHeader, msgIndex, msgsLen)))
}

msgBuffer.Write([]byte("\n\n"))
msgBuffer.Write(contentSnippet)
}

msgs = append(msgs, msgBuffer.String())
}
defer wecomResp.Body.Close()

// 处理请求结果
wecomRespBody, _ := io.ReadAll(wecomResp.Body)
if wecomResp.StatusCode != http.StatusOK || string(wecomRespBody) != okMsg {
e := c.Error(errors.New(string(wecomRespBody)))
e.Meta = "请求企业微信失败,HTTP Code: " + strconv.Itoa(wecomResp.StatusCode)
c.Writer.WriteHeader(http.StatusInternalServerError)
return
for _, msg := range msgs {
// 请求企业微信
postBody, _ := json.Marshal(map[string]interface{}{
"msgtype": "markdown",
"markdown": map[string]interface{}{
"content": msg,
},
})
postBodyBuffer := bytes.NewBuffer(postBody)
wecomResp, err := http.Post(webhookUrl+key, "application/json", postBodyBuffer)
if err != nil {
e := c.Error(err)
e.Meta = "发起企业微信请求失败"
c.Writer.WriteHeader(http.StatusInternalServerError)
return
}

// 处理请求结果
wecomRespBody, _ := io.ReadAll(wecomResp.Body)
wecomResp.Body.Close()
if wecomResp.StatusCode != http.StatusOK || string(wecomRespBody) != okMsg {
e := c.Error(errors.New(string(wecomRespBody)))
e.Meta = "请求企业微信失败,HTTP Code: " + strconv.Itoa(wecomResp.StatusCode)
c.Writer.WriteHeader(http.StatusInternalServerError)
return
}
}

c.Writer.WriteHeader(http.StatusOK)
Expand Down

0 comments on commit b8e11a9

Please sign in to comment.